diff options
Diffstat (limited to 'activejob/lib/active_job/arguments.rb')
-rw-r--r-- | activejob/lib/active_job/arguments.rb | 113 |
1 files changed, 113 insertions, 0 deletions
diff --git a/activejob/lib/active_job/arguments.rb b/activejob/lib/active_job/arguments.rb new file mode 100644 index 0000000000..752be6898e --- /dev/null +++ b/activejob/lib/active_job/arguments.rb @@ -0,0 +1,113 @@ +require 'active_support/core_ext/hash/indifferent_access' + +module ActiveJob + # Raised when an exception is raised during job arguments deserialization. + # + # Wraps the original exception raised as +original_exception+. + class DeserializationError < StandardError + attr_reader :original_exception + + def initialize(e) #:nodoc: + super("Error while trying to deserialize arguments: #{e.message}") + @original_exception = e + set_backtrace e.backtrace + end + end + + # Raised when an unsupported argument type is being set as job argument. We + # currently support NilClass, Fixnum, Float, String, TrueClass, FalseClass, + # Bignum and object that can be represented as GlobalIDs (ex: Active Record). + # Also raised if you set the key for a Hash something else than a string or + # a symbol. + class SerializationError < ArgumentError + end + + module Arguments + extend self + TYPE_WHITELIST = [ NilClass, Fixnum, Float, String, TrueClass, FalseClass, Bignum ] + + # Serializes a set of arguments. Whitelisted types are returned + # as-is. Arrays/Hashes are serialized element by element. + # All other types are serialized using GlobalID. + def serialize(arguments) + arguments.map { |argument| serialize_argument(argument) } + end + + # Deserializes a set of arguments. Whitelisted types are returned + # as-is. Arrays/Hashes are deserialized element by element. + # All other types are deserialized using GlobalID. + def deserialize(arguments) + arguments.map { |argument| deserialize_argument(argument) } + rescue => e + raise DeserializationError.new(e) + end + + private + GLOBALID_KEY = '_aj_globalid'.freeze + private_constant :GLOBALID_KEY + + def serialize_argument(argument) + case argument + when *TYPE_WHITELIST + argument + when GlobalID::Identification + { GLOBALID_KEY => argument.to_global_id.to_s } + when Array + argument.map { |arg| serialize_argument(arg) } + when Hash + argument.each_with_object({}) do |(key, value), hash| + hash[serialize_hash_key(key)] = serialize_argument(value) + end + else + raise SerializationError.new("Unsupported argument type: #{argument.class.name}") + end + end + + def deserialize_argument(argument) + case argument + when String + GlobalID::Locator.locate(argument) || argument + when *TYPE_WHITELIST + argument + when Array + argument.map { |arg| deserialize_argument(arg) } + when Hash + if serialized_global_id?(argument) + deserialize_global_id argument + else + deserialize_hash argument + end + else + raise ArgumentError, "Can only deserialize primitive arguments: #{argument.inspect}" + end + end + + def serialized_global_id?(hash) + hash.size == 1 and hash.include?(GLOBALID_KEY) + end + + def deserialize_global_id(hash) + GlobalID::Locator.locate hash[GLOBALID_KEY] + end + + def deserialize_hash(serialized_hash) + serialized_hash.each_with_object({}.with_indifferent_access) do |(key, value), hash| + hash[key] = deserialize_argument(value) + end + end + + RESERVED_KEYS = [GLOBALID_KEY, GLOBALID_KEY.to_sym] + private_constant :RESERVED_KEYS + + def serialize_hash_key(key) + case key + when *RESERVED_KEYS + raise SerializationError.new("Can't serialize a Hash with reserved key #{key.inspect}") + when String, Symbol + key.to_s + else + raise SerializationError.new("Only string and symbol hash keys may be serialized as job arguments, but #{key.inspect} is a #{key.class}") + end + end + end +end |