From 27cda2fcd4ca8d5bac83ddad6f27fa55bce1c18d Mon Sep 17 00:00:00 2001 From: Anthony Wang Date: Sat, 20 Aug 2022 23:07:11 -0500 Subject: [PATCH] Implement JSONLoad, To, and On functions for ForgeFed types --- go.mod | 2 +- go.sum | 4 +- modules/activitypub/authorize_interaction.go | 11 ++- modules/activitypub/repo.go | 15 ++-- modules/forgefed/branch.go | 71 +++++++++++++---- modules/forgefed/commit.go | 60 ++++++++++++--- modules/forgefed/forgefed.go | 29 +++++++ modules/forgefed/push.go | 71 +++++++++++++---- modules/forgefed/repository.go | 65 +++++++++++++--- modules/forgefed/ticket.go | 80 +++++++++++++++----- 10 files changed, 326 insertions(+), 82 deletions(-) diff --git a/go.mod b/go.mod index 977517cb20..218a6e3808 100644 --- a/go.mod +++ b/go.mod @@ -299,7 +299,7 @@ replace github.com/shurcooL/vfsgen => github.com/lunny/vfsgen v0.0.0-20220105142 replace github.com/satori/go.uuid v1.2.0 => github.com/gofrs/uuid v4.2.0+incompatible -replace github.com/go-ap/activitypub => gitea.com/Ta180m/activitypub v0.0.0-20220711172827-b05423b54985 +replace github.com/go-ap/activitypub => gitea.com/Ta180m/activitypub v0.0.0-20220821033718-79a43a998240 exclude github.com/gofrs/uuid v3.2.0+incompatible diff --git a/go.sum b/go.sum index 6abc53f0bc..d42a4ebd9f 100644 --- a/go.sum +++ b/go.sum @@ -73,8 +73,8 @@ contrib.go.opencensus.io/resource v0.1.1/go.mod h1:F361eGI91LCmW1I/Saf+rX0+OFcig dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078 h1:cliQ4HHsCo6xi2oWZYKWW4bly/Ory9FuTpFPRxj/mAg= git.sr.ht/~mariusor/go-xsd-duration v0.0.0-20220703122237-02e73435a078/go.mod h1:g/V2Hjas6Z1UHUp4yIx6bATpNzJ7DYtD0FG3+xARWxs= -gitea.com/Ta180m/activitypub v0.0.0-20220711172827-b05423b54985 h1:mFjFQxAsUdcvtM3klWtTbkhGnsRVF+DPhZPofsKmPlk= -gitea.com/Ta180m/activitypub v0.0.0-20220711172827-b05423b54985/go.mod h1:KDY/LAOthmTlRA4ft9TKrvPKVe+AZaSaU+3HS/UITvU= +gitea.com/Ta180m/activitypub v0.0.0-20220821033718-79a43a998240 h1:ZW+5jY1ibiRPGoBlUcwBA9eHdFVGD70qWrKXw877Yd4= +gitea.com/Ta180m/activitypub v0.0.0-20220821033718-79a43a998240/go.mod h1:Md4CYDr9vFkojPjSqzG04PN03hgnd+X3jf4i8Nbb7OE= gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb h1:Yy0Bxzc8R2wxiwXoG/rECGplJUSpXqCsog9PuJFgiHs= gitea.com/go-chi/binding v0.0.0-20220309004920-114340dabecb/go.mod h1:77TZu701zMXWJFvB8gvTbQ92zQ3DQq/H7l5wAEjQRKc= gitea.com/go-chi/cache v0.0.0-20210110083709-82c4c9ce2d5e/go.mod h1:k2V/gPDEtXGjjMGuBJiapffAXTv76H4snSmlJRLUhH0= diff --git a/modules/activitypub/authorize_interaction.go b/modules/activitypub/authorize_interaction.go index 0ff2c2af7a..1803aaf9d2 100644 --- a/modules/activitypub/authorize_interaction.go +++ b/modules/activitypub/authorize_interaction.go @@ -27,6 +27,7 @@ func AuthorizeInteraction(ctx *context.Context) { } ap.ItemTyperFunc = forgefed.GetItemByType + ap.JSONItemUnmarshal = forgefed.JSONUnmarshalerFn object, err := ap.UnmarshalJSON(resp) if err != nil { ctx.ServerError("UnmarshalJSON", err) @@ -51,11 +52,19 @@ func AuthorizeInteraction(ctx *context.Context) { } ctx.Redirect(name) case forgefed.RepositoryType: - err = FederatedRepoNew(ctx, object.(forgefed.Repository)) + err = forgefed.OnRepository(object, func(r *forgefed.Repository) error { + return FederatedRepoNew(ctx, r) + }) if err != nil { ctx.ServerError("FederatedRepoNew", err) return } + username, reponame, err := repositoryIRIToName(object.GetLink()) + if err != nil { + ctx.ServerError("repositoryIRIToName", err) + return + } + ctx.Redirect(username+"/"+reponame) } ctx.Status(http.StatusOK) diff --git a/modules/activitypub/repo.go b/modules/activitypub/repo.go index fd44445400..2a716f7059 100644 --- a/modules/activitypub/repo.go +++ b/modules/activitypub/repo.go @@ -8,12 +8,12 @@ import ( "context" "code.gitea.io/gitea/models" - repo_model "code.gitea.io/gitea/models/repo" + repo_service "code.gitea.io/gitea/services/repository" "code.gitea.io/gitea/modules/forgefed" ) // Create a new federated repo from a Repository object -func FederatedRepoNew(ctx context.Context, repository forgefed.Repository) error { +func FederatedRepoNew(ctx context.Context, repository *forgefed.Repository) error { ownerIRI, err := repositoryIRIToOwnerIRI(repository.GetLink()) if err != nil { return err @@ -23,9 +23,14 @@ func FederatedRepoNew(ctx context.Context, repository forgefed.Repository) error return err } - repo := repo_model.Repository{ + // TODO: Check if repo already exists + repo, err := repo_service.CreateRepository(user, user, models.CreateRepoOptions{ Name: repository.Name.String(), + }) + if err != nil { + return err } + if repository.ForkedFrom != nil { repo.IsFork = true forkedFrom, err := repositoryIRIToRepository(ctx, repository.ForkedFrom.GetLink()) @@ -34,7 +39,5 @@ func FederatedRepoNew(ctx context.Context, repository forgefed.Repository) error } repo.ForkID = forkedFrom.ID } - - // TODO: Check if repo already exists - return models.CreateRepository(ctx, user, user, &repo, false) + return nil } diff --git a/modules/forgefed/branch.go b/modules/forgefed/branch.go index d6606c07d6..d2cc71a96b 100644 --- a/modules/forgefed/branch.go +++ b/modules/forgefed/branch.go @@ -5,6 +5,9 @@ package forgefed import ( + "reflect" + "unsafe" + ap "github.com/go-ap/activitypub" "github.com/valyala/fastjson" ) @@ -26,30 +29,68 @@ func BranchNew() *Branch { return &o } -func (br Branch) MarshalJSON() ([]byte, error) { - b, err := br.Object.MarshalJSON() - if len(b) == 0 || err != nil { +func (b Branch) MarshalJSON() ([]byte, error) { + bin, err := b.Object.MarshalJSON() + if len(bin) == 0 || err != nil { return nil, err } - b = b[:len(b)-1] - if br.Ref != nil { - ap.WriteItemJSONProp(&b, "ref", br.Ref) + bin = bin[:len(bin)-1] + if b.Ref != nil { + ap.JSONWriteItemJSONProp(&bin, "ref", b.Ref) } - ap.Write(&b, '}') - return b, nil + ap.JSONWrite(&bin, '}') + return bin, nil } -func (br *Branch) UnmarshalJSON(data []byte) error { +func JSONLoadBranch(val *fastjson.Value, b *Branch) error { + ap.OnObject(&b.Object, func(o *ap.Object) error { + return ap.JSONLoadObject(val, o) + }) + b.Ref = ap.JSONGetItem(val, "ref") + return nil +} + +func (b *Branch) UnmarshalJSON(data []byte) error { p := fastjson.Parser{} val, err := p.ParseBytes(data) if err != nil { return err } - - br.Ref = ap.JSONGetItem(val, "ref") - - return ap.OnObject(&br.Object, func(a *ap.Object) error { - return ap.LoadObject(val, a) - }) + return JSONLoadBranch(val, b) +} + +// ToBranch tries to convert the it Item to a Branch object. +func ToBranch(it ap.Item) (*Branch, error) { + switch i := it.(type) { + case *Branch: + return i, nil + case Branch: + return &i, nil + case *ap.Object: + return (*Branch)(unsafe.Pointer(i)), nil + case ap.Object: + return (*Branch)(unsafe.Pointer(&i)), nil + default: + // NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes + typ := reflect.TypeOf(new(Branch)) + if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Branch); ok { + return i, nil + } + } + return nil, ap.ErrorInvalidType[ap.Object](it) +} + +type withBranchFn func(*Branch) error + +// OnBranch calls function fn on it Item if it can be asserted to type *Branch +func OnBranch(it ap.Item, fn withBranchFn) error { + if it == nil { + return nil + } + ob, err := ToBranch(it) + if err != nil { + return err + } + return fn(ob) } diff --git a/modules/forgefed/commit.go b/modules/forgefed/commit.go index 05b2d48ec4..ff87c3f479 100644 --- a/modules/forgefed/commit.go +++ b/modules/forgefed/commit.go @@ -5,7 +5,9 @@ package forgefed import ( + "reflect" "time" + "unsafe" ap "github.com/go-ap/activitypub" "github.com/valyala/fastjson" @@ -38,26 +40,64 @@ func (c Commit) MarshalJSON() ([]byte, error) { b = b[:len(b)-1] if !c.Created.IsZero() { - ap.WriteTimeJSONProp(&b, "created", c.Created) + ap.JSONWriteTimeJSONProp(&b, "created", c.Created) } if !c.Committed.IsZero() { - ap.WriteTimeJSONProp(&b, "committed", c.Committed) + ap.JSONWriteTimeJSONProp(&b, "committed", c.Committed) } - ap.Write(&b, '}') + ap.JSONWrite(&b, '}') return b, nil } +func JSONLoadCommit(val *fastjson.Value, c *Commit) error { + ap.OnObject(&c.Object, func(o *ap.Object) error { + return ap.JSONLoadObject(val, o) + }) + c.Created = ap.JSONGetTime(val, "created") + c.Committed = ap.JSONGetTime(val, "committed") + return nil +} + func (c *Commit) UnmarshalJSON(data []byte) error { p := fastjson.Parser{} val, err := p.ParseBytes(data) if err != nil { return err } - - c.Created = ap.JSONGetTime(val, "created") - c.Committed = ap.JSONGetTime(val, "committed") - - return ap.OnObject(&c.Object, func(a *ap.Object) error { - return ap.LoadObject(val, a) - }) + return JSONLoadCommit(val, c) +} + +// ToCommit tries to convert the it Item to a Commit object. +func ToCommit(it ap.Item) (*Commit, error) { + switch i := it.(type) { + case *Commit: + return i, nil + case Commit: + return &i, nil + case *ap.Object: + return (*Commit)(unsafe.Pointer(i)), nil + case ap.Object: + return (*Commit)(unsafe.Pointer(&i)), nil + default: + // NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes + typ := reflect.TypeOf(new(Commit)) + if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Commit); ok { + return i, nil + } + } + return nil, ap.ErrorInvalidType[ap.Object](it) +} + +type withCommitFn func(*Commit) error + +// OnCommit calls function fn on it Item if it can be asserted to type *Commit +func OnCommit(it ap.Item, fn withCommitFn) error { + if it == nil { + return nil + } + ob, err := ToCommit(it) + if err != nil { + return err + } + return fn(ob) } diff --git a/modules/forgefed/forgefed.go b/modules/forgefed/forgefed.go index d9f12f2a33..9d6ec7cadc 100644 --- a/modules/forgefed/forgefed.go +++ b/modules/forgefed/forgefed.go @@ -6,6 +6,7 @@ package forgefed import ( ap "github.com/go-ap/activitypub" + "github.com/valyala/fastjson" ) const ForgeFedNamespaceURI = "https://forgefed.org/ns" @@ -27,3 +28,31 @@ func GetItemByType(typ ap.ActivityVocabularyType) (ap.Item, error) { } return ap.GetItemByType(typ) } + +// JSONUnmarshalerFn is the type of the function that will load the data from a fastjson.Value into an Item +// that the go-ap/activitypub package doesn't know about. +func JSONUnmarshalerFn(typ ap.ActivityVocabularyType, val *fastjson.Value, i ap.Item) error { + switch typ { + case CommitType: + return OnCommit(i, func(c *Commit) error { + return JSONLoadCommit(val, c) + }) + case BranchType: + return OnBranch(i, func(b *Branch) error { + return JSONLoadBranch(val, b) + }) + case RepositoryType: + return OnRepository(i, func(r *Repository) error { + return JSONLoadRepository(val, r) + }) + case PushType: + return OnPush(i, func(p *Push) error { + return JSONLoadPush(val, p) + }) + case TicketType: + return OnTicket(i, func(t *Ticket) error { + return JSONLoadTicket(val, t) + }) + } + return nil +} diff --git a/modules/forgefed/push.go b/modules/forgefed/push.go index 937b206ae5..105fd10232 100644 --- a/modules/forgefed/push.go +++ b/modules/forgefed/push.go @@ -5,6 +5,9 @@ package forgefed import ( + "reflect" + "unsafe" + ap "github.com/go-ap/activitypub" "github.com/valyala/fastjson" ) @@ -38,30 +41,68 @@ func (p Push) MarshalJSON() ([]byte, error) { b = b[:len(b)-1] if p.Target != nil { - ap.WriteItemJSONProp(&b, "target", p.Target) + ap.JSONWriteItemJSONProp(&b, "target", p.Target) } if p.HashBefore != nil { - ap.WriteItemJSONProp(&b, "hashBefore", p.HashBefore) + ap.JSONWriteItemJSONProp(&b, "hashBefore", p.HashBefore) } if p.HashAfter != nil { - ap.WriteItemJSONProp(&b, "hashAfter", p.HashAfter) + ap.JSONWriteItemJSONProp(&b, "hashAfter", p.HashAfter) } - ap.Write(&b, '}') + ap.JSONWrite(&b, '}') return b, nil } -func (p *Push) UnmarshalJSON(data []byte) error { - ps := fastjson.Parser{} - val, err := ps.ParseBytes(data) - if err != nil { - return err - } - +func JSONLoadPush(val *fastjson.Value, p *Push) error { + ap.OnObject(&p.Object, func(o *ap.Object) error { + return ap.JSONLoadObject(val, o) + }) p.Target = ap.JSONGetItem(val, "target") p.HashBefore = ap.JSONGetItem(val, "hashBefore") p.HashAfter = ap.JSONGetItem(val, "hashAfter") - - return ap.OnObject(&p.Object, func(a *ap.Object) error { - return ap.LoadObject(val, a) - }) + return nil +} + +func (p *Push) UnmarshalJSON(data []byte) error { + par := fastjson.Parser{} + val, err := par.ParseBytes(data) + if err != nil { + return err + } + return JSONLoadPush(val, p) +} + +// ToPush tries to convert the it Item to a Push object. +func ToPush(it ap.Item) (*Push, error) { + switch i := it.(type) { + case *Push: + return i, nil + case Push: + return &i, nil + case *ap.Object: + return (*Push)(unsafe.Pointer(i)), nil + case ap.Object: + return (*Push)(unsafe.Pointer(&i)), nil + default: + // NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes + typ := reflect.TypeOf(new(Push)) + if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Push); ok { + return i, nil + } + } + return nil, ap.ErrorInvalidType[ap.Object](it) +} + +type withPushFn func(*Push) error + +// OnPush calls function fn on it Item if it can be asserted to type *Push +func OnPush(it ap.Item, fn withPushFn) error { + if it == nil { + return nil + } + ob, err := ToPush(it) + if err != nil { + return err + } + return fn(ob) } diff --git a/modules/forgefed/repository.go b/modules/forgefed/repository.go index f8450d6d8f..931a7ab752 100644 --- a/modules/forgefed/repository.go +++ b/modules/forgefed/repository.go @@ -5,6 +5,9 @@ package forgefed import ( + "reflect" + "unsafe" + ap "github.com/go-ap/activitypub" "github.com/valyala/fastjson" ) @@ -39,30 +42,68 @@ func (r Repository) MarshalJSON() ([]byte, error) { b = b[:len(b)-1] if r.Team != nil { - ap.WriteItemJSONProp(&b, "team", r.Team) + ap.JSONWriteItemJSONProp(&b, "team", r.Team) } if r.Forks != nil { - ap.WriteItemJSONProp(&b, "forks", r.Forks) + ap.JSONWriteItemJSONProp(&b, "forks", r.Forks) } if r.ForkedFrom != nil { - ap.WriteItemJSONProp(&b, "forkedFrom", r.ForkedFrom) + ap.JSONWriteItemJSONProp(&b, "forkedFrom", r.ForkedFrom) } - ap.Write(&b, '}') + ap.JSONWrite(&b, '}') return b, nil } +func JSONLoadRepository(val *fastjson.Value, r *Repository) error { + ap.OnActor(&r.Actor, func(a *ap.Actor) error { + return ap.JSONLoadActor(val, a) + }) + r.Team = ap.JSONGetItem(val, "team") + r.Forks = ap.JSONGetItem(val, "forks") + r.ForkedFrom = ap.JSONGetItem(val, "forkedFrom") + return nil +} + func (r *Repository) UnmarshalJSON(data []byte) error { p := fastjson.Parser{} val, err := p.ParseBytes(data) if err != nil { return err } - - r.Team = ap.JSONGetItem(val, "team") - r.Forks = ap.JSONGetItem(val, "forks") - r.ForkedFrom = ap.JSONGetItem(val, "forkedFrom") - - return ap.OnActor(&r.Actor, func(a *ap.Actor) error { - return ap.LoadActor(val, a) - }) + return JSONLoadRepository(val, r) +} + +// ToRepository tries to convert the it Item to a Repository Actor. +func ToRepository(it ap.Item) (*Repository, error) { + switch i := it.(type) { + case *Repository: + return i, nil + case Repository: + return &i, nil + case *ap.Actor: + return (*Repository)(unsafe.Pointer(i)), nil + case ap.Actor: + return (*Repository)(unsafe.Pointer(&i)), nil + default: + // NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes + typ := reflect.TypeOf(new(Repository)) + if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Repository); ok { + return i, nil + } + } + return nil, ap.ErrorInvalidType[ap.Actor](it) +} + +type withRepositoryFn func(*Repository) error + +// OnRepository calls function fn on it Item if it can be asserted to type *Repository +func OnRepository(it ap.Item, fn withRepositoryFn) error { + if it == nil { + return nil + } + ob, err := ToRepository(it) + if err != nil { + return err + } + return fn(ob) } diff --git a/modules/forgefed/ticket.go b/modules/forgefed/ticket.go index 3281f9765b..f571e7764a 100644 --- a/modules/forgefed/ticket.go +++ b/modules/forgefed/ticket.go @@ -5,7 +5,9 @@ package forgefed import ( + "reflect" "time" + "unsafe" ap "github.com/go-ap/activitypub" "github.com/valyala/fastjson" @@ -48,44 +50,82 @@ func (t Ticket) MarshalJSON() ([]byte, error) { b = b[:len(b)-1] if t.Dependants != nil { - ap.WriteItemCollectionJSONProp(&b, "dependants", t.Dependants) + ap.JSONWriteItemCollectionJSONProp(&b, "dependants", t.Dependants) } if t.Dependencies != nil { - ap.WriteItemCollectionJSONProp(&b, "dependencies", t.Dependencies) + ap.JSONWriteItemCollectionJSONProp(&b, "dependencies", t.Dependencies) } - ap.WriteBoolJSONProp(&b, "isResolved", t.IsResolved) + ap.JSONWriteBoolJSONProp(&b, "isResolved", t.IsResolved) if t.ResolvedBy != nil { - ap.WriteItemJSONProp(&b, "resolvedBy", t.ResolvedBy) + ap.JSONWriteItemJSONProp(&b, "resolvedBy", t.ResolvedBy) } if !t.Resolved.IsZero() { - ap.WriteTimeJSONProp(&b, "resolved", t.Resolved) + ap.JSONWriteTimeJSONProp(&b, "resolved", t.Resolved) } if t.Origin != nil { - ap.WriteItemJSONProp(&b, "origin", t.Origin) + ap.JSONWriteItemJSONProp(&b, "origin", t.Origin) } if t.Target != nil { - ap.WriteItemJSONProp(&b, "target", t.Target) + ap.JSONWriteItemJSONProp(&b, "target", t.Target) } - ap.Write(&b, '}') + ap.JSONWrite(&b, '}') return b, nil } +func JSONLoadTicket(val *fastjson.Value, t *Ticket) error { + ap.OnObject(&t.Object, func(o *ap.Object) error { + return ap.JSONLoadObject(val, o) + }) + t.Dependants = ap.JSONGetItems(val, "dependants") + t.Dependencies = ap.JSONGetItems(val, "dependencies") + t.IsResolved = ap.JSONGetBoolean(val, "isResolved") + t.ResolvedBy = ap.JSONGetItem(val, "resolvedBy") + t.Resolved = ap.JSONGetTime(val, "resolved") + t.Origin = ap.JSONGetItem(val, "origin") + t.Target = ap.JSONGetItem(val, "target") + return nil +} + func (t *Ticket) UnmarshalJSON(data []byte) error { p := fastjson.Parser{} val, err := p.ParseBytes(data) if err != nil { return err } - - t.Dependants = ap.JSONGetItems(val, "dependants") - t.Dependencies = ap.JSONGetItems(val, "dependencies") - t.IsResolved = ap.JSONGetBoolean(val, "isResolved") - t.ResolvedBy = ap.JSONGetItem(val, "resolvedBy") - t.Resolved = ap.JSONGetTime(val, "resolved") - t.Origin = ap.JSONGetItem(val, "origin") - t.Target = ap.JSONGetItem(val, "target") - - return ap.OnObject(&t.Object, func(a *ap.Object) error { - return ap.LoadObject(val, a) - }) + return JSONLoadTicket(val, t) +} + +// ToTicket tries to convert the it Item to a Ticket object. +func ToTicket(it ap.Item) (*Ticket, error) { + switch i := it.(type) { + case *Ticket: + return i, nil + case Ticket: + return &i, nil + case *ap.Object: + return (*Ticket)(unsafe.Pointer(i)), nil + case ap.Object: + return (*Ticket)(unsafe.Pointer(&i)), nil + default: + // NOTE(marius): this is an ugly way of dealing with the interface conversion error: types from different scopes + typ := reflect.TypeOf(new(Ticket)) + if i, ok := reflect.ValueOf(it).Convert(typ).Interface().(*Ticket); ok { + return i, nil + } + } + return nil, ap.ErrorInvalidType[ap.Object](it) +} + +type withTicketFn func(*Ticket) error + +// OnTicket calls function fn on it Item if it can be asserted to type *Ticket +func OnTicket(it ap.Item, fn withTicketFn) error { + if it == nil { + return nil + } + ob, err := ToTicket(it) + if err != nil { + return err + } + return fn(ob) }