mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[chore] internal/ap: add pollable AS types, code reformatting, general niceties (#2248)
This commit is contained in:
@ -20,62 +20,134 @@ package ap
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
|
||||
"github.com/superseriousbusiness/activity/pub"
|
||||
"github.com/superseriousbusiness/activity/streams"
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
)
|
||||
|
||||
// mapPool is a memory pool of maps for JSON decoding.
|
||||
var mapPool = sync.Pool{
|
||||
New: func() any {
|
||||
return make(map[string]any)
|
||||
},
|
||||
}
|
||||
|
||||
// getMap acquires a map from memory pool.
|
||||
func getMap() map[string]any {
|
||||
m := mapPool.Get().(map[string]any) //nolint
|
||||
return m
|
||||
}
|
||||
|
||||
// putMap clears and places map back in pool.
|
||||
func putMap(m map[string]any) {
|
||||
if len(m) > int(^uint8(0)) {
|
||||
// don't pool overly
|
||||
// large maps.
|
||||
return
|
||||
}
|
||||
for k := range m {
|
||||
delete(m, k)
|
||||
}
|
||||
mapPool.Put(m)
|
||||
}
|
||||
|
||||
// ResolveActivity is a util function for pulling a pub.Activity type out of an incoming request body.
|
||||
func ResolveIncomingActivity(r *http.Request) (pub.Activity, gtserror.WithCode) {
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
|
||||
// Tidy up when done.
|
||||
defer r.Body.Close()
|
||||
|
||||
// Decode the JSON body stream into "raw" map.
|
||||
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
|
||||
err := gtserror.Newf("error decoding json: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Resolve "raw" JSON to vocab.Type.
|
||||
t, err := streams.ToType(r.Context(), raw)
|
||||
if err != nil {
|
||||
if !streams.IsUnmatchedErr(err) {
|
||||
err := gtserror.Newf("error matching json to type: %w", err)
|
||||
return nil, gtserror.NewErrorInternalError(err)
|
||||
}
|
||||
|
||||
// Respond with bad request; we just couldn't
|
||||
// match the type to one that we know about.
|
||||
const text = "body json not resolvable as ActivityStreams type"
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
||||
// Ensure this is an Activity type.
|
||||
activity, ok := t.(pub.Activity)
|
||||
if !ok {
|
||||
text := fmt.Sprintf("cannot resolve vocab type %T as pub.Activity", t)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
||||
if activity.GetJSONLDId() == nil {
|
||||
const text = "missing ActivityStreams id property"
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
||||
// Normalize any Statusable, Accountable, Pollable fields found.
|
||||
// (see: https://github.com/superseriousbusiness/gotosocial/issues/1661)
|
||||
NormalizeIncomingActivity(activity, raw)
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
return activity, nil
|
||||
}
|
||||
|
||||
// ResolveStatusable tries to resolve the given bytes into an ActivityPub Statusable representation.
|
||||
// It will then perform normalization on the Statusable.
|
||||
//
|
||||
// Works for: Article, Document, Image, Video, Note, Page, Event, Place, Profile
|
||||
// Works for: Article, Document, Image, Video, Note, Page, Event, Place, Profile, Question.
|
||||
func ResolveStatusable(ctx context.Context, b []byte) (Statusable, error) {
|
||||
rawStatusable := make(map[string]interface{})
|
||||
if err := json.Unmarshal(b, &rawStatusable); err != nil {
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
|
||||
// Unmarshal the raw JSON data in a "raw" JSON map.
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err)
|
||||
}
|
||||
|
||||
t, err := streams.ToType(ctx, rawStatusable)
|
||||
// Resolve an ActivityStreams type from JSON.
|
||||
t, err := streams.ToType(ctx, raw)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
statusable Statusable
|
||||
ok bool
|
||||
)
|
||||
|
||||
switch t.GetTypeName() {
|
||||
case ObjectArticle:
|
||||
statusable, ok = t.(vocab.ActivityStreamsArticle)
|
||||
case ObjectDocument:
|
||||
statusable, ok = t.(vocab.ActivityStreamsDocument)
|
||||
case ObjectImage:
|
||||
statusable, ok = t.(vocab.ActivityStreamsImage)
|
||||
case ObjectVideo:
|
||||
statusable, ok = t.(vocab.ActivityStreamsVideo)
|
||||
case ObjectNote:
|
||||
statusable, ok = t.(vocab.ActivityStreamsNote)
|
||||
case ObjectPage:
|
||||
statusable, ok = t.(vocab.ActivityStreamsPage)
|
||||
case ObjectEvent:
|
||||
statusable, ok = t.(vocab.ActivityStreamsEvent)
|
||||
case ObjectPlace:
|
||||
statusable, ok = t.(vocab.ActivityStreamsPlace)
|
||||
case ObjectProfile:
|
||||
statusable, ok = t.(vocab.ActivityStreamsProfile)
|
||||
}
|
||||
|
||||
// Attempt to cast as Statusable.
|
||||
statusable, ok := ToStatusable(t)
|
||||
if !ok {
|
||||
err = gtserror.Newf("could not resolve %T to Statusable", t)
|
||||
err := gtserror.Newf("cannot resolve vocab type %T as statusable", t)
|
||||
return nil, gtserror.SetWrongType(err)
|
||||
}
|
||||
|
||||
NormalizeIncomingContent(statusable, rawStatusable)
|
||||
NormalizeIncomingAttachments(statusable, rawStatusable)
|
||||
NormalizeIncomingSummary(statusable, rawStatusable)
|
||||
NormalizeIncomingName(statusable, rawStatusable)
|
||||
if pollable, ok := ToPollable(statusable); ok {
|
||||
// Question requires extra normalization, and
|
||||
// fortunately directly implements Statusable.
|
||||
NormalizeIncomingPollOptions(pollable, raw)
|
||||
statusable = pollable
|
||||
}
|
||||
|
||||
NormalizeIncomingContent(statusable, raw)
|
||||
NormalizeIncomingAttachments(statusable, raw)
|
||||
NormalizeIncomingSummary(statusable, raw)
|
||||
NormalizeIncomingName(statusable, raw)
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
return statusable, nil
|
||||
}
|
||||
@ -85,40 +157,32 @@ func ResolveStatusable(ctx context.Context, b []byte) (Statusable, error) {
|
||||
//
|
||||
// Works for: Application, Group, Organization, Person, Service
|
||||
func ResolveAccountable(ctx context.Context, b []byte) (Accountable, error) {
|
||||
rawAccountable := make(map[string]interface{})
|
||||
if err := json.Unmarshal(b, &rawAccountable); err != nil {
|
||||
// Get "raw" map
|
||||
// destination.
|
||||
raw := getMap()
|
||||
|
||||
// Unmarshal the raw JSON data in a "raw" JSON map.
|
||||
if err := json.Unmarshal(b, &raw); err != nil {
|
||||
return nil, gtserror.Newf("error unmarshalling bytes into json: %w", err)
|
||||
}
|
||||
|
||||
t, err := streams.ToType(ctx, rawAccountable)
|
||||
// Resolve an ActivityStreams type from JSON.
|
||||
t, err := streams.ToType(ctx, raw)
|
||||
if err != nil {
|
||||
return nil, gtserror.Newf("error resolving json into ap vocab type: %w", err)
|
||||
}
|
||||
|
||||
var (
|
||||
accountable Accountable
|
||||
ok bool
|
||||
)
|
||||
|
||||
switch t.GetTypeName() {
|
||||
case ActorApplication:
|
||||
accountable, ok = t.(vocab.ActivityStreamsApplication)
|
||||
case ActorGroup:
|
||||
accountable, ok = t.(vocab.ActivityStreamsGroup)
|
||||
case ActorOrganization:
|
||||
accountable, ok = t.(vocab.ActivityStreamsOrganization)
|
||||
case ActorPerson:
|
||||
accountable, ok = t.(vocab.ActivityStreamsPerson)
|
||||
case ActorService:
|
||||
accountable, ok = t.(vocab.ActivityStreamsService)
|
||||
}
|
||||
|
||||
// Attempt to cast as Statusable.
|
||||
accountable, ok := ToAccountable(t)
|
||||
if !ok {
|
||||
err = gtserror.Newf("could not resolve %T to Accountable", t)
|
||||
err := gtserror.Newf("cannot resolve vocab type %T as accountable", t)
|
||||
return nil, gtserror.SetWrongType(err)
|
||||
}
|
||||
|
||||
NormalizeIncomingSummary(accountable, rawAccountable)
|
||||
NormalizeIncomingSummary(accountable, raw)
|
||||
|
||||
// Release.
|
||||
putMap(raw)
|
||||
|
||||
return accountable, nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user