diff options
author | 2023-10-03 14:59:30 +0100 | |
---|---|---|
committer | 2023-10-03 14:59:30 +0100 | |
commit | 297b6eeaaa136f9166e6f5937b5fca917e12fb5b (patch) | |
tree | 25eb815e775efd35c3ccb0b5b932547caf6107fb /internal/ap/resolve.go | |
parent | [chore]: Bump go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp... (diff) | |
download | gotosocial-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.go | 182 |
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 } |