diff --git a/app/lib/activitypub/adapter.rb b/app/lib/activitypub/adapter.rb index 99f4d93055403d39b8ebfb7e1b9d7eea861c08e5..7e0b16c25dcb87c2241fcd745172a17a9a3f63c0 100644 --- a/app/lib/activitypub/adapter.rb +++ b/app/lib/activitypub/adapter.rb @@ -1,30 +1,23 @@ # frozen_string_literal: true class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base - CONTEXT = { - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - - { - 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', - 'sensitive' => 'as:sensitive', - 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' }, - 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' }, - 'Hashtag' => 'as:Hashtag', - 'ostatus' => 'http://ostatus.org#', - 'atomUri' => 'ostatus:atomUri', - 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', - 'conversation' => 'ostatus:conversation', - 'toot' => 'http://joinmastodon.org/ns#', - 'Emoji' => 'toot:Emoji', - 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' }, - 'featured' => { '@id' => 'toot:featured', '@type' => '@id' }, - 'schema' => 'http://schema.org#', - 'PropertyValue' => 'schema:PropertyValue', - 'value' => 'schema:value', - }, - ], + NAMED_CONTEXT_MAP = { + activitystreams: 'https://www.w3.org/ns/activitystreams', + security: 'https://w3id.org/security/v1', + }.freeze + + CONTEXT_EXTENSION_MAP = { + manually_approves_followers: { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' }, + sensitive: { 'sensitive' => 'as:sensitive' }, + hashtag: { 'Hashtag' => 'as:Hashtag' }, + moved_to: { 'movedTo' => { '@id' => 'as:movedTo', '@type' => '@id' } }, + also_known_as: { 'alsoKnownAs' => { '@id' => 'as:alsoKnownAs', '@type' => '@id' } }, + emoji: { 'toot' => 'http://joinmastodon.org/ns#', 'Emoji' => 'toot:Emoji' }, + featured: { 'toot' => 'http://joinmastodon.org/ns#', 'featured' => { '@id' => 'toot:featured', '@type' => '@id' } }, + property_value: { 'schema' => 'http://schema.org#', 'PropertyValue' => 'schema:PropertyValue', 'value' => 'schema:value' }, + atom_uri: { 'ostatus' => 'http://ostatus.org#', 'atomUri' => 'ostatus:atomUri' }, + conversation: { 'ostatus' => 'http://ostatus.org#', 'inReplyToAtomUri' => 'ostatus:inReplyToAtomUri', 'conversation' => 'ostatus:conversation' }, + focal_point: { 'toot' => 'http://joinmastodon.org/ns#', 'focalPoint' => { '@container' => '@list', '@id' => 'toot:focalPoint' } }, }.freeze def self.default_key_transform @@ -36,8 +29,36 @@ class ActivityPub::Adapter < ActiveModelSerializers::Adapter::Base end def serializable_hash(options = nil) - options = serialization_options(options) - serialized_hash = ActiveModelSerializers::Adapter::Attributes.new(serializer, instance_options).serializable_hash(options) - CONTEXT.merge(self.class.transform_key_casing!(serialized_hash, instance_options)) + options = serialization_options(options) + serialized_hash = serializer.serializable_hash(options) + serialized_hash = self.class.transform_key_casing!(serialized_hash, instance_options) + + { '@context' => serialized_context }.merge(serialized_hash) + end + + private + + def serialized_context + context_array = [] + + serializer_options = serializer.send(:instance_options) || {} + named_contexts = [:activitystreams] + serializer._named_contexts.keys + serializer_options.fetch(:named_contexts, {}).keys + context_extensions = serializer._context_extensions.keys + serializer_options.fetch(:context_extensions, {}).keys + + named_contexts.each do |key| + context_array << NAMED_CONTEXT_MAP[key] + end + + extensions = context_extensions.each_with_object({}) do |key, h| + h.merge!(CONTEXT_EXTENSION_MAP[key]) + end + + context_array << extensions unless extensions.empty? + + if context_array.size == 1 + context_array.first + else + context_array + end end end diff --git a/app/lib/activitypub/serializer.rb b/app/lib/activitypub/serializer.rb new file mode 100644 index 0000000000000000000000000000000000000000..07bd8c494612e2117d3d1dd55fc34cd9953ced9b --- /dev/null +++ b/app/lib/activitypub/serializer.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class ActivityPub::Serializer < ActiveModel::Serializer + with_options instance_writer: false, instance_reader: true do |serializer| + serializer.class_attribute :_named_contexts + serializer.class_attribute :_context_extensions + + self._named_contexts ||= {} + self._context_extensions ||= {} + end + + def self.inherited(base) + super + + base._named_contexts = _named_contexts.dup + base._context_extensions = _context_extensions.dup + end + + def self.context(*named_contexts) + named_contexts.each do |context| + _named_contexts[context] = true + end + end + + def self.context_extensions(*extension_names) + extension_names.each do |extension_name| + _context_extensions[extension_name] = true + end + end +end diff --git a/app/serializers/activitypub/accept_follow_serializer.rb b/app/serializers/activitypub/accept_follow_serializer.rb index 3e23591a527492ee5b2869bf1b0293e9d2ffef21..1c1c6ab73bb5783db05276c865d429cadf1fb6c8 100644 --- a/app/serializers/activitypub/accept_follow_serializer.rb +++ b/app/serializers/activitypub/accept_follow_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::AcceptFollowSerializer < ActiveModel::Serializer +class ActivityPub::AcceptFollowSerializer < ActivityPub::Serializer attributes :id, :type, :actor has_one :object, serializer: ActivityPub::FollowSerializer diff --git a/app/serializers/activitypub/activity_serializer.rb b/app/serializers/activitypub/activity_serializer.rb index c001e28aae472b7a11c509280b5996234148cc94..c06d5c87ca3855f0a9c6d6dd20c10ec1eebec1df 100644 --- a/app/serializers/activitypub/activity_serializer.rb +++ b/app/serializers/activitypub/activity_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::ActivitySerializer < ActiveModel::Serializer +class ActivityPub::ActivitySerializer < ActivityPub::Serializer attributes :id, :type, :actor, :published, :to, :cc has_one :proper, key: :object, serializer: ActivityPub::NoteSerializer, if: :serialize_object? diff --git a/app/serializers/activitypub/actor_serializer.rb b/app/serializers/activitypub/actor_serializer.rb index 6746c1782929a94fb622de10ede547f2c822c271..4b982b95511a22fc59fd777c3148dd9aa4223b3c 100644 --- a/app/serializers/activitypub/actor_serializer.rb +++ b/app/serializers/activitypub/actor_serializer.rb @@ -1,8 +1,13 @@ # frozen_string_literal: true -class ActivityPub::ActorSerializer < ActiveModel::Serializer +class ActivityPub::ActorSerializer < ActivityPub::Serializer include RoutingHelper + context :security + + context_extensions :manually_approves_followers, :featured, :also_known_as, + :moved_to, :property_value, :hashtag, :emoji + attributes :id, :type, :following, :followers, :inbox, :outbox, :featured, :preferred_username, :name, :summary, @@ -16,7 +21,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer attribute :moved_to, if: :moved? attribute :also_known_as, if: :also_known_as? - class EndpointsSerializer < ActiveModel::Serializer + class EndpointsSerializer < ActivityPub::Serializer include RoutingHelper attributes :shared_inbox @@ -124,7 +129,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer class CustomEmojiSerializer < ActivityPub::EmojiSerializer end - class TagSerializer < ActiveModel::Serializer + class TagSerializer < ActivityPub::Serializer include RoutingHelper attributes :type, :href, :name @@ -142,7 +147,7 @@ class ActivityPub::ActorSerializer < ActiveModel::Serializer end end - class Account::FieldSerializer < ActiveModel::Serializer + class Account::FieldSerializer < ActivityPub::Serializer attributes :type, :name, :value def type diff --git a/app/serializers/activitypub/add_serializer.rb b/app/serializers/activitypub/add_serializer.rb index c0906e8d036418d5318390b107602a266786a706..6f5aab17f95ddb86e2f864e4fa28b4eafd71d165 100644 --- a/app/serializers/activitypub/add_serializer.rb +++ b/app/serializers/activitypub/add_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::AddSerializer < ActiveModel::Serializer +class ActivityPub::AddSerializer < ActivityPub::Serializer include RoutingHelper attributes :type, :actor, :target diff --git a/app/serializers/activitypub/block_serializer.rb b/app/serializers/activitypub/block_serializer.rb index 624ce2fce8107b9ff37786f7a825a991b37b5069..e6c69329d8cb26c761ba0f1e0f4b22ab6136183d 100644 --- a/app/serializers/activitypub/block_serializer.rb +++ b/app/serializers/activitypub/block_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::BlockSerializer < ActiveModel::Serializer +class ActivityPub::BlockSerializer < ActivityPub::Serializer attributes :id, :type, :actor attribute :virtual_object, key: :object diff --git a/app/serializers/activitypub/collection_serializer.rb b/app/serializers/activitypub/collection_serializer.rb index b03609957d104be9b23f00c7b3bff4423c2e62fa..da1ba735fc25063c69c83e4aedb88796ce7ed55d 100644 --- a/app/serializers/activitypub/collection_serializer.rb +++ b/app/serializers/activitypub/collection_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::CollectionSerializer < ActiveModel::Serializer +class ActivityPub::CollectionSerializer < ActivityPub::Serializer def self.serializer_for(model, options) return ActivityPub::NoteSerializer if model.class.name == 'Status' return ActivityPub::CollectionSerializer if model.class.name == 'ActivityPub::CollectionPresenter' diff --git a/app/serializers/activitypub/delete_actor_serializer.rb b/app/serializers/activitypub/delete_actor_serializer.rb index ddf59be9709ef1cc21af267f935fbb28611ac1cb..a6c5e2385eb266b6e1eed613456adcfe1c92cae3 100644 --- a/app/serializers/activitypub/delete_actor_serializer.rb +++ b/app/serializers/activitypub/delete_actor_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::DeleteActorSerializer < ActiveModel::Serializer +class ActivityPub::DeleteActorSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to attribute :virtual_object, key: :object diff --git a/app/serializers/activitypub/delete_serializer.rb b/app/serializers/activitypub/delete_serializer.rb index 5012a8383ff517003906f65474636f6b7e2da4db..a7d5bd469856fb6ab1e98472249dadfd56d55066 100644 --- a/app/serializers/activitypub/delete_serializer.rb +++ b/app/serializers/activitypub/delete_serializer.rb @@ -1,7 +1,9 @@ # frozen_string_literal: true -class ActivityPub::DeleteSerializer < ActiveModel::Serializer - class TombstoneSerializer < ActiveModel::Serializer +class ActivityPub::DeleteSerializer < ActivityPub::Serializer + class TombstoneSerializer < ActivityPub::Serializer + context_extensions :atom_uri + attributes :id, :type, :atom_uri def id diff --git a/app/serializers/activitypub/emoji_serializer.rb b/app/serializers/activitypub/emoji_serializer.rb index 7b06b1e5db2aa5a23814a977c740040774236b4c..4dc38f3ea6e571d6c0d1a362a9f88fb392cf5b90 100644 --- a/app/serializers/activitypub/emoji_serializer.rb +++ b/app/serializers/activitypub/emoji_serializer.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true -class ActivityPub::EmojiSerializer < ActiveModel::Serializer +class ActivityPub::EmojiSerializer < ActivityPub::Serializer include RoutingHelper + context_extensions :emoji + attributes :id, :type, :name, :updated has_one :icon, serializer: ActivityPub::ImageSerializer diff --git a/app/serializers/activitypub/flag_serializer.rb b/app/serializers/activitypub/flag_serializer.rb index 1e7a46dd9ed486a215a65ed74129b5390acb2c61..2f2a707d36299719edb2ca0973b997ac9f81fd7a 100644 --- a/app/serializers/activitypub/flag_serializer.rb +++ b/app/serializers/activitypub/flag_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::FlagSerializer < ActiveModel::Serializer +class ActivityPub::FlagSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :content attribute :virtual_object, key: :object diff --git a/app/serializers/activitypub/follow_serializer.rb b/app/serializers/activitypub/follow_serializer.rb index bb204ee8f36916e4b1a3a4ca582f86861b93249e..9228d7716e31d7b91f617857775dce81d74f923a 100644 --- a/app/serializers/activitypub/follow_serializer.rb +++ b/app/serializers/activitypub/follow_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::FollowSerializer < ActiveModel::Serializer +class ActivityPub::FollowSerializer < ActivityPub::Serializer attributes :id, :type, :actor attribute :virtual_object, key: :object diff --git a/app/serializers/activitypub/image_serializer.rb b/app/serializers/activitypub/image_serializer.rb index 3c08f77e8313985ff63d42d13dd0870efc2c4998..1060f969149b03cd7a4a5bb87f2813f53afbfd25 100644 --- a/app/serializers/activitypub/image_serializer.rb +++ b/app/serializers/activitypub/image_serializer.rb @@ -1,8 +1,10 @@ # frozen_string_literal: true -class ActivityPub::ImageSerializer < ActiveModel::Serializer +class ActivityPub::ImageSerializer < ActivityPub::Serializer include RoutingHelper + context_extensions :focal_point + attributes :type, :media_type, :url attribute :focal_point, if: :focal_point? diff --git a/app/serializers/activitypub/like_serializer.rb b/app/serializers/activitypub/like_serializer.rb index c1a7ff6f63743e210afd128004d2523c5af0f7af..0f170ddb4e82777d2e2ae8e4b5bf2e3199b6e29f 100644 --- a/app/serializers/activitypub/like_serializer.rb +++ b/app/serializers/activitypub/like_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::LikeSerializer < ActiveModel::Serializer +class ActivityPub::LikeSerializer < ActivityPub::Serializer attributes :id, :type, :actor attribute :virtual_object, key: :object diff --git a/app/serializers/activitypub/note_serializer.rb b/app/serializers/activitypub/note_serializer.rb index 553f333d8ff00aa22302894ab45a1d92db931ef3..0666bea5a786627cf92794fda117f8dfddea6514 100644 --- a/app/serializers/activitypub/note_serializer.rb +++ b/app/serializers/activitypub/note_serializer.rb @@ -1,6 +1,9 @@ # frozen_string_literal: true -class ActivityPub::NoteSerializer < ActiveModel::Serializer +class ActivityPub::NoteSerializer < ActivityPub::Serializer + context_extensions :atom_uri, :conversation, :sensitive, + :hashtag, :emoji, :focal_point + attributes :id, :type, :summary, :in_reply_to, :published, :url, :attributed_to, :to, :cc, :sensitive, @@ -147,7 +150,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer object.poll&.expired? end - class MediaAttachmentSerializer < ActiveModel::Serializer + class MediaAttachmentSerializer < ActivityPub::Serializer include RoutingHelper attributes :type, :media_type, :url, :name @@ -178,7 +181,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer end end - class MentionSerializer < ActiveModel::Serializer + class MentionSerializer < ActivityPub::Serializer attributes :type, :href, :name def type @@ -194,7 +197,7 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer end end - class TagSerializer < ActiveModel::Serializer + class TagSerializer < ActivityPub::Serializer include RoutingHelper attributes :type, :href, :name @@ -215,8 +218,8 @@ class ActivityPub::NoteSerializer < ActiveModel::Serializer class CustomEmojiSerializer < ActivityPub::EmojiSerializer end - class OptionSerializer < ActiveModel::Serializer - class RepliesSerializer < ActiveModel::Serializer + class OptionSerializer < ActivityPub::Serializer + class RepliesSerializer < ActivityPub::Serializer attributes :type, :total_items def type diff --git a/app/serializers/activitypub/public_key_serializer.rb b/app/serializers/activitypub/public_key_serializer.rb index 38e9e93baaac97dc6d9b42676df9b875ea0c9116..62ed49e81d27657fc0fc624fb3985052df53ad0a 100644 --- a/app/serializers/activitypub/public_key_serializer.rb +++ b/app/serializers/activitypub/public_key_serializer.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -class ActivityPub::PublicKeySerializer < ActiveModel::Serializer +class ActivityPub::PublicKeySerializer < ActivityPub::Serializer + context :security + attributes :id, :owner, :public_key_pem def id diff --git a/app/serializers/activitypub/reject_follow_serializer.rb b/app/serializers/activitypub/reject_follow_serializer.rb index 7814f4f5759d450b8a7d58c1b0a9345c767634f2..4996c9a3c35455e76dadc94d68f3b8d2e525da83 100644 --- a/app/serializers/activitypub/reject_follow_serializer.rb +++ b/app/serializers/activitypub/reject_follow_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::RejectFollowSerializer < ActiveModel::Serializer +class ActivityPub::RejectFollowSerializer < ActivityPub::Serializer attributes :id, :type, :actor has_one :object, serializer: ActivityPub::FollowSerializer diff --git a/app/serializers/activitypub/remove_serializer.rb b/app/serializers/activitypub/remove_serializer.rb index c2a5ae1b3a8ac496cf866e27946c308e1f3c5a1c..7fefda59da3ee839cb8196d4b5eed9f21b44249b 100644 --- a/app/serializers/activitypub/remove_serializer.rb +++ b/app/serializers/activitypub/remove_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::RemoveSerializer < ActiveModel::Serializer +class ActivityPub::RemoveSerializer < ActivityPub::Serializer include RoutingHelper attributes :type, :actor, :target diff --git a/app/serializers/activitypub/undo_announce_serializer.rb b/app/serializers/activitypub/undo_announce_serializer.rb index 4fc042727ad6898aae581d824332ba6df3372421..6758af6792afdd4afc845a7d74a92aad6c33bf2d 100644 --- a/app/serializers/activitypub/undo_announce_serializer.rb +++ b/app/serializers/activitypub/undo_announce_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UndoAnnounceSerializer < ActiveModel::Serializer +class ActivityPub::UndoAnnounceSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to has_one :object, serializer: ActivityPub::ActivitySerializer diff --git a/app/serializers/activitypub/undo_block_serializer.rb b/app/serializers/activitypub/undo_block_serializer.rb index 2f43d8402a38a259ade75353940c5a18f96bc72e..b4f049377715504b1b2b3de64d5cb2bc7f70d804 100644 --- a/app/serializers/activitypub/undo_block_serializer.rb +++ b/app/serializers/activitypub/undo_block_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UndoBlockSerializer < ActiveModel::Serializer +class ActivityPub::UndoBlockSerializer < ActivityPub::Serializer attributes :id, :type, :actor has_one :object, serializer: ActivityPub::BlockSerializer diff --git a/app/serializers/activitypub/undo_follow_serializer.rb b/app/serializers/activitypub/undo_follow_serializer.rb index e5b7f143d793428a7859cbe0439f5606b901a4bf..9b3e0ca3cb23b1c10045a930f660744c67bb2fbc 100644 --- a/app/serializers/activitypub/undo_follow_serializer.rb +++ b/app/serializers/activitypub/undo_follow_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UndoFollowSerializer < ActiveModel::Serializer +class ActivityPub::UndoFollowSerializer < ActivityPub::Serializer attributes :id, :type, :actor has_one :object, serializer: ActivityPub::FollowSerializer diff --git a/app/serializers/activitypub/undo_like_serializer.rb b/app/serializers/activitypub/undo_like_serializer.rb index 25f4ccaaed5ac120fc777f165f199b305c7b08bd..20c786cb76966c3257f29efe81ff1039a2e5973b 100644 --- a/app/serializers/activitypub/undo_like_serializer.rb +++ b/app/serializers/activitypub/undo_like_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UndoLikeSerializer < ActiveModel::Serializer +class ActivityPub::UndoLikeSerializer < ActivityPub::Serializer attributes :id, :type, :actor has_one :object, serializer: ActivityPub::LikeSerializer diff --git a/app/serializers/activitypub/update_poll_serializer.rb b/app/serializers/activitypub/update_poll_serializer.rb index f7933346febc2c3e9268eed4c5582d4b79be9532..a9a09747f8ce098d28d0c47f13c0c4fd76e0991d 100644 --- a/app/serializers/activitypub/update_poll_serializer.rb +++ b/app/serializers/activitypub/update_poll_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UpdatePollSerializer < ActiveModel::Serializer +class ActivityPub::UpdatePollSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to has_one :object, serializer: ActivityPub::NoteSerializer diff --git a/app/serializers/activitypub/update_serializer.rb b/app/serializers/activitypub/update_serializer.rb index 48d7a192946a70875e79fc5ee8f6d44e605674c4..a5eb857d3fe6dce8245c4bff5ffb170157c2fce7 100644 --- a/app/serializers/activitypub/update_serializer.rb +++ b/app/serializers/activitypub/update_serializer.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class ActivityPub::UpdateSerializer < ActiveModel::Serializer +class ActivityPub::UpdateSerializer < ActivityPub::Serializer attributes :id, :type, :actor, :to has_one :object, serializer: ActivityPub::ActorSerializer diff --git a/app/serializers/activitypub/vote_serializer.rb b/app/serializers/activitypub/vote_serializer.rb index 248190404e840f4f71a16e787a9feffca8021503..71ef5c77e3daf07881ab904c5c4360cf64f17c80 100644 --- a/app/serializers/activitypub/vote_serializer.rb +++ b/app/serializers/activitypub/vote_serializer.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true -class ActivityPub::VoteSerializer < ActiveModel::Serializer - class NoteSerializer < ActiveModel::Serializer +class ActivityPub::VoteSerializer < ActivityPub::Serializer + class NoteSerializer < ActivityPub::Serializer attributes :id, :type, :name, :attributed_to, :in_reply_to, :to diff --git a/config/initializers/active_model_serializers.rb b/config/initializers/active_model_serializers.rb index 0e69e1d96c408ded0bb5d4d08a5b132408709771..329a5fb2c3a005688bdf9a5e023815008a62f974 100644 --- a/config/initializers/active_model_serializers.rb +++ b/config/initializers/active_model_serializers.rb @@ -3,3 +3,22 @@ ActiveModelSerializers.config.tap do |config| end ActiveSupport::Notifications.unsubscribe(ActiveModelSerializers::Logging::RENDER_EVENT) + +class ActiveModel::Serializer::Reflection + # We monkey-patch this method so that when we include associations in a serializer, + # the nested serializers can send information about used contexts upwards back to + # the root. We do this via instance_options because the nesting can be dynamic. + def build_association(parent_serializer, parent_serializer_options, include_slice = {}) + serializer = options[:serializer] + + parent_serializer_options.merge!(named_contexts: serializer._named_contexts, context_extensions: serializer._context_extensions) if serializer.respond_to?(:_named_contexts) + + association_options = { + parent_serializer: parent_serializer, + parent_serializer_options: parent_serializer_options, + include_slice: include_slice, + } + + ActiveModel::Serializer::Association.new(self, association_options) + end +end diff --git a/spec/lib/activitypub/adapter_spec.rb b/spec/lib/activitypub/adapter_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..ea03797aab2819a7a5da31456ae7928150cc9d1a --- /dev/null +++ b/spec/lib/activitypub/adapter_spec.rb @@ -0,0 +1,88 @@ +require 'rails_helper' + +RSpec.describe ActivityPub::Adapter do + class TestObject < ActiveModelSerializers::Model + attributes :foo + end + + class TestWithBasicContextSerializer < ActivityPub::Serializer + attributes :foo + end + + class TestWithNamedContextSerializer < ActivityPub::Serializer + context :security + attributes :foo + end + + class TestWithNestedNamedContextSerializer < ActivityPub::Serializer + attributes :foo + + has_one :virtual_object, key: :baz, serializer: TestWithNamedContextSerializer + + def virtual_object + object + end + end + + class TestWithContextExtensionSerializer < ActivityPub::Serializer + context_extensions :sensitive + attributes :foo + end + + class TestWithNestedContextExtensionSerializer < ActivityPub::Serializer + context_extensions :manually_approves_followers + attributes :foo + + has_one :virtual_object, key: :baz, serializer: TestWithContextExtensionSerializer + + def virtual_object + object + end + end + + describe '#serializable_hash' do + let(:serializer_class) {} + + subject { ActiveModelSerializers::SerializableResource.new(TestObject.new(foo: 'bar'), serializer: serializer_class, adapter: described_class).as_json } + + context 'when serializer defines no context' do + let(:serializer_class) { TestWithBasicContextSerializer } + + it 'renders a basic @context' do + expect(subject).to include({ '@context' => 'https://www.w3.org/ns/activitystreams' }) + end + end + + context 'when serializer defines a named context' do + let(:serializer_class) { TestWithNamedContextSerializer } + + it 'renders a @context with both items' do + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }) + end + end + + context 'when serializer has children that define a named context' do + let(:serializer_class) { TestWithNestedNamedContextSerializer } + + it 'renders a @context with both items' do + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1'] }) + end + end + + context 'when serializer defines context extensions' do + let(:serializer_class) { TestWithContextExtensionSerializer } + + it 'renders a @context with the extension' do + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'sensitive' => 'as:sensitive' }] }) + end + end + + context 'when serializer has children that define context extensions' do + let(:serializer_class) { TestWithNestedContextExtensionSerializer } + + it 'renders a @context with both extensions' do + expect(subject).to include({ '@context' => ['https://www.w3.org/ns/activitystreams', { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'sensitive' => 'as:sensitive' }] }) + end + end + end +end