# 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:PERMITTED_TYPES=[NilClass,String,Integer,Float,BigDecimal,TrueClass,FalseClass]# :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:PERMITTED_TYPES,:RESERVED_KEYS,:GLOBALID_KEY,:SYMBOL_KEYS_KEY,:RUBY2_KEYWORDS_KEY,:WITH_INDIFFERENT_ACCESS_KEYunlessHash.respond_to?(:ruby2_keywords_hash?)&&Hash.respond_to?(:ruby2_keywords_hash)usingModule.new{refineHashdoclass<<Hashdefruby2_keywords_hash?(hash)!new(*[hash]).default.equal?(hash)enddefruby2_keywords_hash(hash)_ruby2_keywords_hash(**hash)endprivatedef_ruby2_keywords_hash(*args)args.lastendruby2_keywords(:_ruby2_keywords_hash)endend}enddefserialize_argument(argument)caseargumentwhen*PERMITTED_TYPESargumentwhenGlobalID::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_keysresultwhen->(arg){arg.respond_to?(:permitted?)&&arg.respond_to?(:to_h)}serialize_indifferent_hash(argument.to_h)elseSerializers.serialize(argument)endenddefdeserialize_argument(argument)caseargumentwhenStringargumentwhen*PERMITTED_TYPESargumentwhenArrayargument.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