activesupport/lib/active_support/messages/serializer_with_fallback.rb
# frozen_string_literal: true
require "active_support/core_ext/kernel/reporting"
require "active_support/notifications"
module ActiveSupport
module Messages # :nodoc:
module SerializerWithFallback # :nodoc:
def self.[](format)
if format.to_s.include?("message_pack") && !defined?(ActiveSupport::MessagePack)
require "active_support/message_pack"
end
SERIALIZERS.fetch(format)
end
def load(dumped)
format = detect_format(dumped)
if format == self.format
_load(dumped)
elsif format && fallback?(format)
payload = { serializer: SERIALIZERS.key(self), fallback: format, serialized: dumped }
ActiveSupport::Notifications.instrument("message_serializer_fallback.active_support", payload) do
payload[:deserialized] = SERIALIZERS[format]._load(dumped)
end
else
raise "Unsupported serialization format"
end
end
private
def detect_format(dumped)
case
when MessagePackWithFallback.dumped?(dumped)
:message_pack
when MarshalWithFallback.dumped?(dumped)
:marshal
when JsonWithFallback.dumped?(dumped)
:json
end
end
def fallback?(format)
format != :marshal
end
module AllowMarshal
private
def fallback?(format)
super || format == :marshal
end
end
module MarshalWithFallback
include SerializerWithFallback
extend self
def format
:marshal
end
def dump(object)
Marshal.dump(object)
end
def _load(dumped)
Marshal.load(dumped)
end
MARSHAL_SIGNATURE = "\x04\x08"
def dumped?(dumped)
dumped.start_with?(MARSHAL_SIGNATURE)
end
end
module JsonWithFallback
include SerializerWithFallback
extend self
def format
:json
end
def dump(object)
ActiveSupport::JSON.encode(object)
end
def _load(dumped)
ActiveSupport::JSON.decode(dumped)
end
JSON_START_WITH = /\A(?:[{\["]|-?\d|true|false|null)/
def dumped?(dumped)
JSON_START_WITH.match?(dumped)
end
private
def detect_format(dumped)
# Assume JSON format if format could not be determined.
super || :json
end
end
module JsonWithFallbackAllowMarshal
include JsonWithFallback
include AllowMarshal
extend self
end
module MessagePackWithFallback
include SerializerWithFallback
extend self
def format
:message_pack
end
def dump(object)
ActiveSupport::MessagePack.dump(object)
end
def _load(dumped)
ActiveSupport::MessagePack.load(dumped)
end
def dumped?(dumped)
available? && ActiveSupport::MessagePack.signature?(dumped)
end
private
def available?
return @available if defined?(@available)
silence_warnings { require "active_support/message_pack" }
@available = true
rescue LoadError
@available = false
end
end
module MessagePackWithFallbackAllowMarshal
include MessagePackWithFallback
include AllowMarshal
extend self
end
SERIALIZERS = {
marshal: MarshalWithFallback,
json: JsonWithFallback,
json_allow_marshal: JsonWithFallbackAllowMarshal,
message_pack: MessagePackWithFallback,
message_pack_allow_marshal: MessagePackWithFallbackAllowMarshal,
}
end
end
end