Implement JSONLoad, To, and On functions for ForgeFed types

This commit is contained in:
Anthony Wang 2022-08-20 23:07:11 -05:00
parent 6a6c6b3481
commit 27cda2fcd4
No known key found for this signature in database
GPG Key ID: BC96B00AEC5F2D76
10 changed files with 326 additions and 82 deletions

2
go.mod
View File

@ -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

4
go.sum
View File

@ -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=

View File

@ -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)

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}