summaryrefslogtreecommitdiff
path: root/internal/ap
diff options
context:
space:
mode:
Diffstat (limited to 'internal/ap')
-rw-r--r--internal/ap/activitystreams.go29
-rw-r--r--internal/ap/ap_test.go10
-rw-r--r--internal/ap/extract.go28
-rw-r--r--internal/ap/interfaces.go115
-rw-r--r--internal/ap/properties.go179
-rw-r--r--internal/ap/serialize.go4
6 files changed, 320 insertions, 45 deletions
diff --git a/internal/ap/activitystreams.go b/internal/ap/activitystreams.go
index 56ebc4909..9c2464410 100644
--- a/internal/ap/activitystreams.go
+++ b/internal/ap/activitystreams.go
@@ -23,15 +23,18 @@ import (
"code.superseriousbusiness.org/activity/pub"
)
-// PublicURI returns a fresh copy of the *url.URL version of the
-// magic ActivityPub URI https://www.w3.org/ns/activitystreams#Public
-func PublicURI() *url.URL {
- publicURI, err := url.Parse(pub.PublicActivityPubIRI)
+// publicIRI is a pre-parsed global public IRI instance.
+var publicIRI = func() *url.URL {
+ url, err := url.Parse(pub.PublicActivityPubIRI)
if err != nil {
panic(err)
}
- return publicURI
-}
+ return url
+}()
+
+// PublicIRI returns a fresh copy of the *url.URL version of the
+// magic ActivityPub URI https://www.w3.org/ns/activitystreams#Public
+func PublicIRI() *url.URL { var u url.URL; u = *publicIRI; return &u }
// https://www.w3.org/TR/activitystreams-vocabulary
const (
@@ -102,9 +105,12 @@ const (
/* GtS stuff */
- ObjectLikeApproval = "LikeApproval"
- ObjectReplyApproval = "ReplyApproval"
- ObjectAnnounceApproval = "AnnounceApproval"
+ ActivityLikeRequest = "LikeRequest"
+ ActivityReplyRequest = "ReplyRequest"
+ ActivityAnnounceRequest = "AnnounceRequest"
+ ObjectLikeAuthorization = "LikeAuthorization"
+ ObjectReplyAuthorization = "ReplyAuthorization"
+ ObjectAnnounceAuthorization = "AnnounceAuthorization"
/* Funkwhale stuff */
@@ -138,7 +144,10 @@ func isActivity(typeName string) bool {
ActivityAnnounce,
ActivityBlock,
ActivityFlag,
- ActivityDislike:
+ ActivityDislike,
+ ActivityLikeRequest,
+ ActivityReplyRequest,
+ ActivityAnnounceRequest:
return true
default:
return false
diff --git a/internal/ap/ap_test.go b/internal/ap/ap_test.go
index 0956fee66..9f275434d 100644
--- a/internal/ap/ap_test.go
+++ b/internal/ap/ap_test.go
@@ -110,7 +110,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
// Anyone can like.
canLikeAlwaysProp := streams.NewGoToSocialAlwaysProperty()
- canLikeAlwaysProp.AppendIRI(ap.PublicURI())
+ canLikeAlwaysProp.AppendIRI(ap.PublicIRI())
canLike.SetGoToSocialAlways(canLikeAlwaysProp)
// Empty approvalRequired.
@@ -127,7 +127,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
// Anyone can reply.
canReplyAlwaysProp := streams.NewGoToSocialAlwaysProperty()
- canReplyAlwaysProp.AppendIRI(ap.PublicURI())
+ canReplyAlwaysProp.AppendIRI(ap.PublicIRI())
canReply.SetGoToSocialAlways(canReplyAlwaysProp)
// Set empty approvalRequired.
@@ -150,7 +150,7 @@ func noteWithMentions1() vocab.ActivityStreamsNote {
// Public requires approval to announce.
canAnnounceApprovalRequiredProp := streams.NewGoToSocialApprovalRequiredProperty()
- canAnnounceApprovalRequiredProp.AppendIRI(ap.PublicURI())
+ canAnnounceApprovalRequiredProp.AppendIRI(ap.PublicIRI())
canAnnounce.SetGoToSocialApprovalRequired(canAnnounceApprovalRequiredProp)
// Set canAnnounce on the policy.
@@ -265,7 +265,7 @@ func addressable1() ap.Addressable {
note := streams.NewActivityStreamsNote()
toProp := streams.NewActivityStreamsToProperty()
- toProp.AppendIRI(ap.PublicURI())
+ toProp.AppendIRI(ap.PublicIRI())
note.SetActivityStreamsTo(toProp)
@@ -287,7 +287,7 @@ func addressable2() ap.Addressable {
note.SetActivityStreamsTo(toProp)
ccProp := streams.NewActivityStreamsCcProperty()
- ccProp.AppendIRI(ap.PublicURI())
+ ccProp.AppendIRI(ap.PublicIRI())
note.SetActivityStreamsCc(ccProp)
diff --git a/internal/ap/extract.go b/internal/ap/extract.go
index 7a727b005..f26b02a82 100644
--- a/internal/ap/extract.go
+++ b/internal/ap/extract.go
@@ -36,7 +36,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/util"
)
-// ExtractObjects will extract object vocab.Types from given implementing interface.
+// ExtractObjects will extract object TypeOrIRIs from given implementing interface.
func ExtractObjects(with WithObject) []TypeOrIRI {
// Extract the attached object (if any).
objProp := with.GetActivityStreamsObject()
@@ -58,6 +58,28 @@ func ExtractObjects(with WithObject) []TypeOrIRI {
return objs
}
+// ExtractInstrument will extract instrument TypeOrIRIs from given implementing interface.
+func ExtractInstruments(with WithInstrument) []TypeOrIRI {
+ // Extract the attached instrument (if any).
+ instrProp := with.GetActivityStreamsInstrument()
+ if instrProp == nil {
+ return nil
+ }
+
+ // Check for invalid len.
+ if instrProp.Len() == 0 {
+ return nil
+ }
+
+ // Accumulate all of the instruments into a slice.
+ instrs := make([]TypeOrIRI, instrProp.Len())
+ for i := range instrProp.Len() {
+ instrs[i] = instrProp.At(i)
+ }
+
+ return instrs
+}
+
// ExtractActivityData will extract the usable data type (e.g. Note, Question, etc) and corresponding JSON, from activity.
func ExtractActivityData(activity pub.Activity, rawJSON map[string]any) ([]TypeOrIRI, []any, bool) {
switch typeName := activity.GetTypeName(); {
@@ -1222,14 +1244,14 @@ func ExtractInteractionPolicy(
statusable Statusable,
owner *gtsmodel.Account,
) *gtsmodel.InteractionPolicy {
- ipa, ok := statusable.(InteractionPolicyAware)
+ wip, ok := statusable.(WithInteractionPolicy)
if !ok {
// Not a type with interaction
// policy properties settable.
return nil
}
- policyProp := ipa.GetGoToSocialInteractionPolicy()
+ policyProp := wip.GetGoToSocialInteractionPolicy()
if policyProp == nil || policyProp.Len() != 1 {
return nil
}
diff --git a/internal/ap/interfaces.go b/internal/ap/interfaces.go
index ec961f80b..3224cecc0 100644
--- a/internal/ap/interfaces.go
+++ b/internal/ap/interfaces.go
@@ -143,13 +143,13 @@ func ToAcceptable(t vocab.Type) (Acceptable, bool) {
return acceptable, true
}
-// IsApprovable returns whether AS vocab type name
-// is something that can be cast to Approvable.
-func IsApprovable(typeName string) bool {
+// IsAuthorizationable returns whether AS vocab type name
+// is something that can be cast to Authorizationable.
+func IsAuthorizationable(typeName string) bool {
switch typeName {
- case ObjectLikeApproval,
- ObjectReplyApproval,
- ObjectAnnounceApproval:
+ case ObjectLikeAuthorization,
+ ObjectReplyAuthorization,
+ ObjectAnnounceAuthorization:
return true
default:
return false
@@ -157,12 +157,12 @@ func IsApprovable(typeName string) bool {
}
// ToAcceptable safely tries to cast vocab.Type as Approvable.
-func ToApprovable(t vocab.Type) (Approvable, bool) {
- approvable, ok := t.(Approvable)
- if !ok || !IsApprovable(t.GetTypeName()) {
+func ToAuthorizationable(t vocab.Type) (Authorizationable, bool) {
+ authable, ok := t.(Authorizationable)
+ if !ok || !IsAuthorizationable(t.GetTypeName()) {
return nil, false
}
- return approvable, true
+ return authable, true
}
// IsAttachmentable returns whether AS vocab type name
@@ -188,6 +188,36 @@ func ToAttachmentable(t vocab.Type) (Attachmentable, bool) {
return attachmentable, true
}
+// IsAnnounceable returns whether AS vocab type name
+// is something that can be cast to vocab.ActivityStreamsAnnounce.
+func IsAnnounceable(typeName string) bool {
+ return typeName == ActivityAnnounce
+}
+
+// ToAnnounceable safely tries to cast vocab.Type as vocab.ActivityStreamsAnnounce.
+func ToAnnounceable(t vocab.Type) (vocab.ActivityStreamsAnnounce, bool) {
+ announceable, ok := t.(vocab.ActivityStreamsAnnounce)
+ if !ok || t.GetTypeName() != ActivityAnnounce {
+ return nil, false
+ }
+ return announceable, true
+}
+
+// IsLikeable returns whether AS vocab type name
+// is something that can be cast to vocab.ActivityStreamsLike.
+func IsLikeable(typeName string) bool {
+ return typeName == ActivityLike
+}
+
+// ToAnnouncToLikeableeable safely tries to cast vocab.Type as vocab.ActivityStreamsLike.
+func ToLikeable(t vocab.Type) (vocab.ActivityStreamsLike, bool) {
+ likeable, ok := t.(vocab.ActivityStreamsLike)
+ if !ok || t.GetTypeName() != ActivityLike {
+ return nil, false
+ }
+ return likeable, true
+}
+
// Activityable represents the minimum activitypub interface for representing an 'activity'.
// (see: IsActivityable() for types implementing this, though you MUST make sure to check
// the typeName as this bare interface may be implementable by non-Activityable types).
@@ -258,11 +288,6 @@ type Statusable interface {
WithReplies
}
-type InteractionPolicyAware interface {
- WithInteractionPolicy
- WithApprovedBy
-}
-
// Pollable represents the minimum activitypub interface for representing a 'poll' (it's a subset of a status).
// (see: IsPollable() for types implementing this, though you MUST make sure to check
// the typeName as this bare interface may be implementable by non-Pollable types).
@@ -299,14 +324,14 @@ type Acceptable interface {
WithResult
}
-// Approvable represents the minimum activitypub interface
-// for a LikeApproval, ReplyApproval, or AnnounceApproval.
-type Approvable interface {
+// Authorizationable represents the minimum interface for a
+// LikeAuthorization, ReplyAuthorization, AnnounceAuthorization.
+type Authorizationable interface {
vocab.Type
WithAttributedTo
- WithObject
- WithTarget
+ WithInteractingObject
+ WithInteractionTarget
}
// Attachmentable represents the minimum activitypub interface for representing a 'mediaAttachment'. (see: IsAttachmentable).
@@ -392,6 +417,16 @@ type ReplyToable interface {
WithInReplyTo
}
+// InteractionRequestable represents the minimum interface for an interaction request
+// activity, eg., LikeRequest, ReplyRequest, AnnounceRequest, QuoteRequest, etc..
+type InteractionRequestable interface {
+ vocab.Type
+
+ WithActor
+ WithObject
+ WithInstrument
+}
+
// CollectionIterator represents the minimum interface for interacting with a
// wrapped Collection or OrderedCollection in order to access next / prev items.
type CollectionIterator interface {
@@ -683,6 +718,12 @@ type WithObject interface {
SetActivityStreamsObject(vocab.ActivityStreamsObjectProperty)
}
+// WithInstrument represents an activity with ActivityStreamsInstrumentProperty
+type WithInstrument interface {
+ GetActivityStreamsInstrument() vocab.ActivityStreamsInstrumentProperty
+ SetActivityStreamsInstrument(vocab.ActivityStreamsInstrumentProperty)
+}
+
// WithTarget represents an activity with ActivityStreamsTargetProperty
type WithTarget interface {
GetActivityStreamsTarget() vocab.ActivityStreamsTargetProperty
@@ -775,14 +816,44 @@ type WithPolicyRules interface {
GetGoToSocialApprovalRequired() vocab.GoToSocialApprovalRequiredProperty // Deprecated
}
-// WithApprovedBy represents a Statusable with the approvedBy property.
+// WithApprovedBy represents an object with the approvedBy property.
type WithApprovedBy interface {
GetGoToSocialApprovedBy() vocab.GoToSocialApprovedByProperty
SetGoToSocialApprovedBy(vocab.GoToSocialApprovedByProperty)
}
-// WithVotersCount represents an activity or object the result property.
+// WithLikeAuthorization represents a Likeable with the likeAuthorization property.
+type WithLikeAuthorization interface {
+ GetGoToSocialLikeAuthorization() vocab.GoToSocialLikeAuthorizationProperty
+ SetGoToSocialLikeAuthorization(vocab.GoToSocialLikeAuthorizationProperty)
+}
+
+// WithReplyAuthorization represents a statusable with the replyAuthorization property.
+type WithReplyAuthorization interface {
+ GetGoToSocialReplyAuthorization() vocab.GoToSocialReplyAuthorizationProperty
+ SetGoToSocialReplyAuthorization(vocab.GoToSocialReplyAuthorizationProperty)
+}
+
+// WithAnnounceAuthorization represents an Announceable with the announceAuthorization property.
+type WithAnnounceAuthorization interface {
+ GetGoToSocialAnnounceAuthorization() vocab.GoToSocialAnnounceAuthorizationProperty
+ SetGoToSocialAnnounceAuthorization(vocab.GoToSocialAnnounceAuthorizationProperty)
+}
+
+// WithResult represents an activity or object with the result property.
type WithResult interface {
GetActivityStreamsResult() vocab.ActivityStreamsResultProperty
SetActivityStreamsResult(vocab.ActivityStreamsResultProperty)
}
+
+// WithInteractingObject represents an activity or object with the InteractingObject property.
+type WithInteractingObject interface {
+ GetGoToSocialInteractingObject() vocab.GoToSocialInteractingObjectProperty
+ SetGoToSocialInteractingObject(vocab.GoToSocialInteractingObjectProperty)
+}
+
+// WithInteractionTarget represents an activity or object with the InteractionTarget property.
+type WithInteractionTarget interface {
+ GetGoToSocialInteractionTarget() vocab.GoToSocialInteractionTargetProperty
+ SetGoToSocialInteractionTarget(vocab.GoToSocialInteractionTargetProperty)
+}
diff --git a/internal/ap/properties.go b/internal/ap/properties.go
index 3e064bae0..51e4ab1c0 100644
--- a/internal/ap/properties.go
+++ b/internal/ap/properties.go
@@ -226,6 +226,36 @@ func AppendObjectIRIs(with WithObject, object ...*url.URL) {
}, object...)
}
+// AppendInstrumentIRIs appends the given IRIs to the Instrument property of 'with'.
+func AppendInstrumentIRIs(with WithInstrument, instrument ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsInstrumentPropertyIterator] {
+ instrumentProp := with.GetActivityStreamsInstrument()
+ if instrumentProp == nil {
+ instrumentProp = streams.NewActivityStreamsInstrumentProperty()
+ with.SetActivityStreamsInstrument(instrumentProp)
+ }
+ return instrumentProp
+ }, instrument...)
+}
+
+// GetResultIRIs returns the IRIs contained in the `result` property of 'with'.
+func GetResultIRIs(with WithResult) []*url.URL {
+ resultProp := with.GetActivityStreamsResult()
+ return extractIRIs(resultProp)
+}
+
+// AppendResultIRIs appends the given IRIs to the Result property of 'with'.
+func AppendResultIRIs(with WithResult, result ...*url.URL) {
+ appendIRIs(func() Property[vocab.ActivityStreamsResultPropertyIterator] {
+ resultProp := with.GetActivityStreamsResult()
+ if resultProp == nil {
+ resultProp = streams.NewActivityStreamsResultProperty()
+ with.SetActivityStreamsResult(resultProp)
+ }
+ return resultProp
+ }, result...)
+}
+
// GetTargetIRIs returns the IRIs contained in the Target property of 'with'.
func GetTargetIRIs(with WithTarget) []*url.URL {
targetProp := with.GetActivityStreamsTarget()
@@ -262,6 +292,42 @@ func AppendAttributedTo(with WithAttributedTo, attribTo ...*url.URL) {
}, attribTo...)
}
+// GetInteractingObject returns IRIs contained in the interactingObject property of 'with'.
+func GetInteractingObject(with WithInteractingObject) []*url.URL {
+ intObjProp := with.GetGoToSocialInteractingObject()
+ return getIRIs(intObjProp)
+}
+
+// AppendInteractingObject appends the given IRIs to the interactingObject property of 'with'.
+func AppendInteractingObject(with WithInteractingObject, interactingObject ...*url.URL) {
+ appendIRIs(func() Property[vocab.GoToSocialInteractingObjectPropertyIterator] {
+ intObjProp := with.GetGoToSocialInteractingObject()
+ if intObjProp == nil {
+ intObjProp = streams.NewGoToSocialInteractingObjectProperty()
+ with.SetGoToSocialInteractingObject(intObjProp)
+ }
+ return intObjProp
+ }, interactingObject...)
+}
+
+// GetInteractionTarget returns IRIs contained in the interactionTarget property of 'with'.
+func GetInteractionTarget(with WithInteractionTarget) []*url.URL {
+ intTargetProp := with.GetGoToSocialInteractionTarget()
+ return getIRIs(intTargetProp)
+}
+
+// AppendInteractionTarget appends the given IRIs to the interactionTarget property of 'with'.
+func AppendInteractionTarget(with WithInteractionTarget, interactionTarget ...*url.URL) {
+ appendIRIs(func() Property[vocab.GoToSocialInteractionTargetPropertyIterator] {
+ intTargetProp := with.GetGoToSocialInteractionTarget()
+ if intTargetProp == nil {
+ intTargetProp = streams.NewGoToSocialInteractionTargetProperty()
+ with.SetGoToSocialInteractionTarget(intTargetProp)
+ }
+ return intTargetProp
+ }, interactionTarget...)
+}
+
// GetInReplyTo returns the IRIs contained in the InReplyTo property of 'with'.
func GetInReplyTo(with WithInReplyTo) []*url.URL {
replyProp := with.GetActivityStreamsInReplyTo()
@@ -607,11 +673,11 @@ func SetHidesCcPublicFromUnauthedWeb(with WithHidesCcPublicFromUnauthedWeb, hide
// GetApprovedBy returns the URL contained in
// the ApprovedBy property of 'with', if set.
func GetApprovedBy(with WithApprovedBy) *url.URL {
- mafProp := with.GetGoToSocialApprovedBy()
- if mafProp == nil || !mafProp.IsIRI() {
+ abProp := with.GetGoToSocialApprovedBy()
+ if abProp == nil || !abProp.IsIRI() {
return nil
}
- return mafProp.Get()
+ return abProp.Get()
}
// SetApprovedBy sets the given url
@@ -625,6 +691,69 @@ func SetApprovedBy(with WithApprovedBy, approvedBy *url.URL) {
abProp.Set(approvedBy)
}
+// GetLikeAuthorization returns the URL contained in
+// the likeAuthorization property of 'with', if set.
+func GetLikeAuthorization(with WithLikeAuthorization) *url.URL {
+ laProp := with.GetGoToSocialLikeAuthorization()
+ if laProp == nil || !laProp.IsIRI() {
+ return nil
+ }
+ return laProp.Get()
+}
+
+// SetLikeAuthorization sets the given url on
+// the 'likeAuthorization' property of 'with'.
+func SetLikeAuthorization(with WithLikeAuthorization, likeAuthorization *url.URL) {
+ laProp := with.GetGoToSocialLikeAuthorization()
+ if laProp == nil {
+ laProp = streams.NewGoToSocialLikeAuthorizationProperty()
+ with.SetGoToSocialLikeAuthorization(laProp)
+ }
+ laProp.Set(likeAuthorization)
+}
+
+// GetReplyAuthorization returns the URL contained in
+// the replyAuthorization property of 'with', if set.
+func GetReplyAuthorization(with WithReplyAuthorization) *url.URL {
+ raProp := with.GetGoToSocialReplyAuthorization()
+ if raProp == nil || !raProp.IsIRI() {
+ return nil
+ }
+ return raProp.Get()
+}
+
+// SetReplyAuthorization sets the given url on
+// the 'replyAuthorization' property of 'with'.
+func SetReplyAuthorization(with WithReplyAuthorization, replyAuthorization *url.URL) {
+ raProp := with.GetGoToSocialReplyAuthorization()
+ if raProp == nil {
+ raProp = streams.NewGoToSocialReplyAuthorizationProperty()
+ with.SetGoToSocialReplyAuthorization(raProp)
+ }
+ raProp.Set(replyAuthorization)
+}
+
+// GetAnnounceAuthorization returns the URL contained in
+// the announceAuthorization property of 'with', if set.
+func GetAnnounceAuthorization(with WithAnnounceAuthorization) *url.URL {
+ aaProp := with.GetGoToSocialAnnounceAuthorization()
+ if aaProp == nil || !aaProp.IsIRI() {
+ return nil
+ }
+ return aaProp.Get()
+}
+
+// SetAnnounceAuthorization sets the given url on
+// the 'announceAuthorization' property of 'with'.
+func SetAnnounceAuthorization(with WithAnnounceAuthorization, announceAuthorization *url.URL) {
+ aaProp := with.GetGoToSocialAnnounceAuthorization()
+ if aaProp == nil {
+ aaProp = streams.NewGoToSocialAnnounceAuthorizationProperty()
+ with.SetGoToSocialAnnounceAuthorization(aaProp)
+ }
+ aaProp.Set(announceAuthorization)
+}
+
// GetMediaType returns the string contained in
// the MediaType property of 'with', if set.
func GetMediaType(with WithMediaType) string {
@@ -689,6 +818,50 @@ func SetBlurhash(with WithBlurhash, mediaType string) {
bProp.Set(mediaType)
}
+// AppendSensitive appends the given sensitive
+// boolean to the `sensitive` property of 'with'.
+func AppendSensitive(with WithSensitive, sensitive bool) {
+ sProp := with.GetActivityStreamsSensitive()
+ if sProp == nil {
+ sProp = streams.NewActivityStreamsSensitiveProperty()
+ with.SetActivityStreamsSensitive(sProp)
+ }
+ sProp.AppendXMLSchemaBoolean(sensitive)
+}
+
+// AppendContent appends the given content
+// string to the `content` property of 'with'.
+func AppendContent(with WithContent, content string) {
+ cProp := with.GetActivityStreamsContent()
+ if cProp == nil {
+ cProp = streams.NewActivityStreamsContentProperty()
+ with.SetActivityStreamsContent(cProp)
+ }
+ cProp.AppendXMLSchemaString(content)
+}
+
+// AppendContentMap appends the given content
+// language map to the `content` property of 'with'.
+func AppendContentMap(with WithContent, contentMap map[string]string) {
+ cProp := with.GetActivityStreamsContent()
+ if cProp == nil {
+ cProp = streams.NewActivityStreamsContentProperty()
+ with.SetActivityStreamsContent(cProp)
+ }
+ cProp.AppendRDFLangString(contentMap)
+}
+
+// SetReplies sets the given replies collection
+// to the `replies` property of 'with'.
+func SetReplies(with WithReplies, replies vocab.ActivityStreamsCollection) {
+ rProp := with.GetActivityStreamsReplies()
+ if rProp == nil {
+ rProp = streams.NewActivityStreamsRepliesProperty()
+ with.SetActivityStreamsReplies(rProp)
+ }
+ rProp.SetActivityStreamsCollection(replies)
+}
+
// extractIRIs extracts just the AP IRIs from an iterable
// property that may contain types (with IRIs) or just IRIs.
//
diff --git a/internal/ap/serialize.go b/internal/ap/serialize.go
index c64c14d75..10af16ea4 100644
--- a/internal/ap/serialize.go
+++ b/internal/ap/serialize.go
@@ -153,8 +153,8 @@ func serializeStatusable(t vocab.Type, includeContext bool) (map[string]interfac
NormalizeOutgoingAttachmentProp(statusable, data)
NormalizeOutgoingContentProp(statusable, data)
- if ipa, ok := statusable.(InteractionPolicyAware); ok {
- NormalizeOutgoingInteractionPolicyProp(ipa, data)
+ if wip, ok := statusable.(WithInteractionPolicy); ok {
+ NormalizeOutgoingInteractionPolicyProp(wip, data)
}
return data, nil