2023-04-06 12:16:53 +01:00
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package ap
import (
"github.com/superseriousbusiness/activity/streams"
"github.com/superseriousbusiness/activity/streams/vocab"
2023-11-21 14:13:30 +00:00
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
2023-04-06 12:16:53 +01:00
)
2023-05-09 11:16:10 +01:00
// Serialize is a custom serializer for ActivityStreams types.
//
// In most cases, it will simply call the go-fed streams.Serialize function under the hood.
// However, if custom serialization is required on a specific type (eg for inter-implementation
// compatibility), it can be inserted into the switch as necessary.
//
// Callers should always call this function instead of streams.Serialize, unless there's a
// very good reason to do otherwise.
//
// Currently, the following things will be custom serialized:
//
2023-11-21 14:13:30 +00:00
// - OrderedCollection: 'orderedItems' property will always be made into an array.
2023-12-10 11:36:00 +00:00
// - OrderedCollectionPage: 'orderedItems' property will always be made into an array.
2023-11-21 14:13:30 +00:00
// - Any Accountable type: 'attachment' property will always be made into an array.
2024-07-26 11:04:28 +01:00
// - Any Statusable type: 'attachment' property will always be made into an array; 'content', 'contentMap', and 'interactionPolicy' will be normalized.
2023-11-21 14:13:30 +00:00
// - Any Activityable type: any 'object's set on an activity will be custom serialized as above.
2023-05-09 11:16:10 +01:00
func Serialize ( t vocab . Type ) ( m map [ string ] interface { } , e error ) {
2023-11-21 14:13:30 +00:00
switch tn := t . GetTypeName ( ) ; {
2023-12-10 11:36:00 +00:00
case tn == ObjectOrderedCollection ||
tn == ObjectOrderedCollectionPage :
return serializeWithOrderedItems ( t )
2023-11-21 14:13:30 +00:00
case IsAccountable ( tn ) :
2023-05-09 11:16:10 +01:00
return serializeAccountable ( t , true )
2023-11-21 14:13:30 +00:00
case IsStatusable ( tn ) :
return serializeStatusable ( t , true )
case IsActivityable ( tn ) :
return serializeActivityable ( t , true )
2023-05-09 11:16:10 +01:00
default :
// No custom serializer necessary.
return streams . Serialize ( t )
}
}
2023-12-10 11:36:00 +00:00
// serializeWithOrderedItems is a custom serializer
// for any type that has an `orderedItems` property.
// Unlike the standard streams.Serialize function,
// this serializer normalizes the orderedItems
// value to always be an array/slice, regardless
// of how many items are contained therein.
2023-04-06 12:16:53 +01:00
//
// See:
// - https://github.com/go-fed/activity/issues/139
// - https://github.com/mastodon/mastodon/issues/24225
2023-12-10 11:36:00 +00:00
func serializeWithOrderedItems ( t vocab . Type ) ( map [ string ] interface { } , error ) {
2023-11-21 14:13:30 +00:00
data , err := streams . Serialize ( t )
2023-04-06 12:16:53 +01:00
if err != nil {
return nil , err
}
2023-05-09 11:16:10 +01:00
orderedItems , ok := data [ "orderedItems" ]
if ! ok {
// No 'orderedItems', nothing to change.
return data , nil
}
if _ , ok := orderedItems . ( [ ] interface { } ) ; ok {
// Already slice.
return data , nil
}
// Coerce single-object to slice.
data [ "orderedItems" ] = [ ] interface { } { orderedItems }
return data , nil
2023-04-06 12:16:53 +01:00
}
2023-05-09 11:16:10 +01:00
// SerializeAccountable is a custom serializer for any Accountable type.
2024-02-06 09:45:46 +00:00
// This serializer rewrites certain values of the Accountable, if present,
// to always be an array/slice.
2023-05-09 11:16:10 +01:00
//
2024-02-06 09:45:46 +00:00
// While this may not always be strictly necessary in json-ld terms, most other
// fedi implementations look for certain fields to be an array and will not parse
// single-entry, non-array fields on accounts properly.
2023-05-09 11:16:10 +01:00
//
// If the accountable is being serialized as a top-level object (eg., for serving
// in response to an account dereference request), then includeContext should be
// set to true, so as to include the json-ld '@context' entries in the data.
// If the accountable is being serialized as part of another object (eg., as the
// object of an activity), then includeContext should be set to false, as the
// @context entry should be included on the top-level/wrapping activity/object.
2023-11-21 14:13:30 +00:00
func serializeAccountable ( t vocab . Type , includeContext bool ) ( map [ string ] interface { } , error ) {
accountable , ok := t . ( Accountable )
if ! ok {
return nil , gtserror . Newf ( "vocab.Type %T not accountable" , t )
}
2023-05-09 11:16:10 +01:00
var (
data map [ string ] interface { }
err error
)
if includeContext {
data , err = streams . Serialize ( accountable )
} else {
data , err = accountable . Serialize ( )
}
if err != nil {
return nil , err
}
2023-11-21 14:13:30 +00:00
NormalizeOutgoingAttachmentProp ( accountable , data )
2024-02-06 09:45:46 +00:00
NormalizeOutgoingAlsoKnownAsProp ( accountable , data )
2023-05-09 11:16:10 +01:00
return data , nil
}
2023-11-21 14:13:30 +00:00
func serializeStatusable ( t vocab . Type , includeContext bool ) ( map [ string ] interface { } , error ) {
statusable , ok := t . ( Statusable )
2023-04-06 12:16:53 +01:00
if ! ok {
2023-11-21 14:13:30 +00:00
return nil , gtserror . Newf ( "vocab.Type %T not statusable" , t )
}
var (
data map [ string ] interface { }
err error
)
if includeContext {
data , err = streams . Serialize ( statusable )
} else {
data , err = statusable . Serialize ( )
2023-05-09 11:16:10 +01:00
}
if err != nil {
return nil , err
}
2023-11-21 14:13:30 +00:00
NormalizeOutgoingAttachmentProp ( statusable , data )
NormalizeOutgoingContentProp ( statusable , data )
2024-07-26 11:04:28 +01:00
NormalizeOutgoingInteractionPolicyProp ( statusable , data )
2023-11-21 14:13:30 +00:00
return data , nil
}
func serializeActivityable ( t vocab . Type , includeContext bool ) ( map [ string ] interface { } , error ) {
activityable , ok := t . ( Activityable )
if ! ok {
return nil , gtserror . Newf ( "vocab.Type %T not activityable" , t )
2023-05-09 11:16:10 +01:00
}
2023-11-21 14:13:30 +00:00
var (
data map [ string ] interface { }
err error
)
if includeContext {
data , err = streams . Serialize ( activityable )
} else {
data , err = activityable . Serialize ( )
2023-04-06 12:16:53 +01:00
}
2023-11-21 14:13:30 +00:00
if err != nil {
return nil , err
2023-05-09 11:16:10 +01:00
}
2023-11-21 14:13:30 +00:00
if err := NormalizeOutgoingObjectProp ( activityable , data ) ; err != nil {
return nil , err
2023-05-09 11:16:10 +01:00
}
2023-04-06 12:16:53 +01:00
2023-05-09 11:16:10 +01:00
return data , nil
2023-04-06 12:16:53 +01:00
}