summaryrefslogtreecommitdiff
path: root/internal/ap/resolve.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-10-03 14:59:30 +0100
committerLibravatar GitHub <noreply@github.com>2023-10-03 14:59:30 +0100
commit297b6eeaaa136f9166e6f5937b5fca917e12fb5b (patch)
tree25eb815e775efd35c3ccb0b5b932547caf6107fb /internal/ap/resolve.go
parent[chore]: Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp... (diff)
downloadgotosocial-297b6eeaaa136f9166e6f5937b5fca917e12fb5b.tar.xz
[chore] internal/ap: add pollable AS types, code reformatting, general niceties (#2248)
Diffstat (limited to 'internal/ap/resolve.go')
-rw-r--r--internal/ap/resolve.go182
1 files changed, 123 insertions, 59 deletions
diff --git a/internal/ap/resolve.go b/internal/ap/resolve.go
index a9955be3f..61f187da0 100644
--- a/internal/ap/resolve.go
+++ b/internal/ap/resolve.go
@@ -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
}