# frozen_string_literal: truerequire"bigdecimal"require"active_support/core_ext/hash"moduleActiveJob# Raised when an exception is raised during job arguments deserialization.## Wraps the original exception raised as +cause+.classDeserializationError<StandardErrordefinitialize# :nodoc:super("Error while trying to deserialize arguments: #{$!.message}")set_backtrace$!.backtraceendend# Raised when an unsupported argument type is set as a job argument. We# currently support String, Integer, Float, NilClass, TrueClass, FalseClass,# BigDecimal, Symbol, Date, Time, DateTime, ActiveSupport::TimeWithZone,# ActiveSupport::Duration, Hash, ActiveSupport::HashWithIndifferentAccess,# Array, Range, or GlobalID::Identification instances, although this can be# extended by adding custom serializers.# Raised if you set the key for a Hash something else than a string or# a symbol. Also raised when trying to serialize an object which can't be# identified with a GlobalID - such as an unpersisted Active Record model.classSerializationError<ArgumentError;endmoduleArgumentsextendself# Serializes a set of arguments. Intrinsic types that can safely be# serialized without mutation are returned as-is. Arrays/Hashes are# serialized element by element. All other types are serialized using# GlobalID.defserialize(arguments)arguments.map{|argument|serialize_argument(argument)}end# Deserializes a set of arguments. Intrinsic types that can safely be# deserialized without mutation are returned as-is. Arrays/Hashes are# deserialized element by element. All other types are deserialized using# GlobalID.defdeserialize(arguments)arguments.map{|argument|deserialize_argument(argument)}rescueraiseDeserializationErrorendprivate# :nodoc:GLOBALID_KEY="_aj_globalid"# :nodoc:SYMBOL_KEYS_KEY="_aj_symbol_keys"# :nodoc:RUBY2_KEYWORDS_KEY="_aj_ruby2_keywords"# :nodoc:WITH_INDIFFERENT_ACCESS_KEY="_aj_hash_with_indifferent_access"# :nodoc:OBJECT_SERIALIZER_KEY="_aj_serialized"# :nodoc:RESERVED_KEYS=[GLOBALID_KEY,GLOBALID_KEY.to_sym,SYMBOL_KEYS_KEY,SYMBOL_KEYS_KEY.to_sym,RUBY2_KEYWORDS_KEY,RUBY2_KEYWORDS_KEY.to_sym,OBJECT_SERIALIZER_KEY,OBJECT_SERIALIZER_KEY.to_sym,WITH_INDIFFERENT_ACCESS_KEY,WITH_INDIFFERENT_ACCESS_KEY.to_sym,]private_constant:RESERVED_KEYS,:GLOBALID_KEY,:SYMBOL_KEYS_KEY,:RUBY2_KEYWORDS_KEY,:WITH_INDIFFERENT_ACCESS_KEYdefserialize_argument(argument)caseargumentwhennil,true,false,Integer,Float# Types that can hardly be subclassedargumentwhenStringifargument.class==StringargumentelsebeginSerializers.serialize(argument)rescueSerializationErrorargumentendendwhenGlobalID::Identificationconvert_to_global_id_hash(argument)whenArrayargument.map{|arg|serialize_argument(arg)}whenActiveSupport::HashWithIndifferentAccessserialize_indifferent_hash(argument)whenHashsymbol_keys=argument.each_key.grep(Symbol).map!(&:to_s)aj_hash_key=ifHash.ruby2_keywords_hash?(argument)RUBY2_KEYWORDS_KEYelseSYMBOL_KEYS_KEYendresult=serialize_hash(argument)result[aj_hash_key]=symbol_keysresultelseifargument.respond_to?(:permitted?)&&argument.respond_to?(:to_h)serialize_indifferent_hash(argument.to_h)elsifBigDecimal===argument&&!ActiveJob.use_big_decimal_serializerActiveJob.deprecator.warn(<<~MSG)
Primitive serialization of BigDecimal job arguments is deprecated as it may serialize via .to_s using certain queue adapters.
Enable config.active_job.use_big_decimal_serializer to use BigDecimalSerializer instead, which will be mandatory in Rails 7.2.
Note that if your application has multiple replicas, you should only enable this setting after successfully deploying your app to Rails 7.1 first.
This will ensure that during your deployment all replicas are capable of deserializing arguments serialized with BigDecimalSerializer.
MSGargumentelseSerializers.serialize(argument)endendenddefdeserialize_argument(argument)caseargumentwhennil,true,false,String,Integer,FloatargumentwhenBigDecimal# BigDecimal may have been legacy serialized; Remove in 7.2argumentwhenArrayargument.map{|arg|deserialize_argument(arg)}whenHashifserialized_global_id?(argument)deserialize_global_idargumentelsifcustom_serialized?(argument)Serializers.deserialize(argument)elsedeserialize_hash(argument)endelseraiseArgumentError,"Can only deserialize primitive arguments: #{argument.inspect}"endenddefserialized_global_id?(hash)hash.size==1&&hash.include?(GLOBALID_KEY)enddefdeserialize_global_id(hash)GlobalID::Locator.locatehash[GLOBALID_KEY]enddefcustom_serialized?(hash)hash.key?(OBJECT_SERIALIZER_KEY)enddefserialize_hash(argument)argument.each_with_object({})do|(key,value),hash|hash[serialize_hash_key(key)]=serialize_argument(value)endenddefdeserialize_hash(serialized_hash)result=serialized_hash.transform_values{|v|deserialize_argument(v)}ifresult.delete(WITH_INDIFFERENT_ACCESS_KEY)result=result.with_indifferent_accesselsifsymbol_keys=result.delete(SYMBOL_KEYS_KEY)result=transform_symbol_keys(result,symbol_keys)elsifsymbol_keys=result.delete(RUBY2_KEYWORDS_KEY)result=transform_symbol_keys(result,symbol_keys)result=Hash.ruby2_keywords_hash(result)endresultenddefserialize_hash_key(key)casekeywhen*RESERVED_KEYSraiseSerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}")whenString,Symbolkey.to_selseraiseSerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}")endenddefserialize_indifferent_hash(indifferent_hash)result=serialize_hash(indifferent_hash)result[WITH_INDIFFERENT_ACCESS_KEY]=serialize_argument(true)resultenddeftransform_symbol_keys(hash,symbol_keys)# NOTE: HashWithIndifferentAccess#transform_keys always# returns stringified keys with indifferent access# so we call #to_h here to ensure keys are symbolized.hash.to_h.transform_keysdo|key|ifsymbol_keys.include?(key)key.to_symelsekeyendendenddefconvert_to_global_id_hash(argument){GLOBALID_KEY=>argument.to_global_id.to_s}rescueURI::GID::MissingModelIdErrorraiseSerializationError,"Unable to serialize #{argument.class} "\"without an id. (Maybe you forgot to call save?)"endendend