# frozen_string_literal: true require "bigdecimal" require "date" require "ipaddr" require "pathname" require "uri/generic" require "msgpack/bigint" require "active_support/hash_with_indifferent_access" require "active_support/time" module ActiveSupport module MessagePack class UnserializableObjectError < StandardError; end class MissingClassError < StandardError; end # :nodoc: module Extensions # :nodoc: extend self def install(registry) registry.register_type 0, Symbol, packer: :to_msgpack_ext, unpacker: :from_msgpack_ext, optimized_symbols_parsing: true registry.register_type 1, Integer, packer: ::MessagePack::Bigint.method(:to_msgpack_ext), unpacker: ::MessagePack::Bigint.method(:from_msgpack_ext), oversized_integer_extension: true registry.register_type 2, BigDecimal, packer: :_dump, unpacker: :_load registry.register_type 3, Rational, packer: method(:write_rational), unpacker: method(:read_rational), recursive: true registry.register_type 4, Complex, packer: method(:write_complex), unpacker: method(:read_complex), recursive: true registry.register_type 5, DateTime, packer: method(:write_datetime), unpacker: method(:read_datetime), recursive: true registry.register_type 6, Date, packer: method(:write_date), unpacker: method(:read_date), recursive: true registry.register_type 7, Time, packer: method(:write_time), unpacker: method(:read_time), recursive: true registry.register_type 8, ActiveSupport::TimeWithZone, packer: method(:write_time_with_zone), unpacker: method(:read_time_with_zone), recursive: true registry.register_type 9, ActiveSupport::TimeZone, packer: method(:dump_time_zone), unpacker: method(:load_time_zone) registry.register_type 10, ActiveSupport::Duration, packer: method(:write_duration), unpacker: method(:read_duration), recursive: true registry.register_type 11, Range, packer: method(:write_range), unpacker: method(:read_range), recursive: true registry.register_type 12, Set, packer: method(:write_set), unpacker: method(:read_set), recursive: true registry.register_type 13, URI::Generic, packer: :to_s, unpacker: URI.method(:parse) registry.register_type 14, IPAddr, packer: :to_s, unpacker: :new registry.register_type 15, Pathname, packer: :to_s, unpacker: :new registry.register_type 16, Regexp, packer: :to_s, unpacker: :new registry.register_type 17, ActiveSupport::HashWithIndifferentAccess, packer: method(:write_hash_with_indifferent_access), unpacker: method(:read_hash_with_indifferent_access), recursive: true end def install_unregistered_type_error(registry) registry.register_type 127, Object, packer: method(:raise_unserializable), unpacker: method(:raise_invalid_format) end def install_unregistered_type_fallback(registry) registry.register_type 127, Object, packer: method(:write_object), unpacker: method(:read_object), recursive: true end def write_rational(rational, packer) packer.write(rational.numerator) packer.write(rational.denominator) unless rational.numerator.zero? end def read_rational(unpacker) numerator = unpacker.read Rational(numerator, numerator.zero? ? 1 : unpacker.read) end def write_complex(complex, packer) packer.write(complex.real) packer.write(complex.imaginary) end def read_complex(unpacker) Complex(unpacker.read, unpacker.read) end def write_datetime(datetime, packer) packer.write(datetime.jd) packer.write(datetime.hour) packer.write(datetime.min) packer.write(datetime.sec) write_rational(datetime.sec_fraction, packer) write_rational(datetime.offset, packer) end def read_datetime(unpacker) DateTime.jd(unpacker.read, unpacker.read, unpacker.read, unpacker.read + read_rational(unpacker), read_rational(unpacker)) end def write_date(date, packer) packer.write(date.jd) end def read_date(unpacker) Date.jd(unpacker.read) end def write_time(time, packer) packer.write(time.tv_sec) packer.write(time.tv_nsec) packer.write(time.utc_offset) end def read_time(unpacker) Time.at_without_coercion(unpacker.read, unpacker.read, :nanosecond, in: unpacker.read) end def write_time_with_zone(twz, packer) write_time(twz.utc, packer) write_time_zone(twz.time_zone, packer) end def read_time_with_zone(unpacker) ActiveSupport::TimeWithZone.new(read_time(unpacker), read_time_zone(unpacker)) end def dump_time_zone(time_zone) time_zone.name end def load_time_zone(name) ActiveSupport::TimeZone[name] end def write_time_zone(time_zone, packer) packer.write(dump_time_zone(time_zone)) end def read_time_zone(unpacker) load_time_zone(unpacker.read) end def write_duration(duration, packer) packer.write(duration.value) packer.write(duration._parts.values_at(*ActiveSupport::Duration::PARTS)) end def read_duration(unpacker) value = unpacker.read parts = ActiveSupport::Duration::PARTS.zip(unpacker.read).to_h parts.compact! ActiveSupport::Duration.new(value, parts) end def write_range(range, packer) packer.write(range.begin) packer.write(range.end) packer.write(range.exclude_end?) end def read_range(unpacker) Range.new(unpacker.read, unpacker.read, unpacker.read) end def write_set(set, packer) packer.write(set.to_a) end def read_set(unpacker) Set.new(unpacker.read) end def write_hash_with_indifferent_access(hwia, packer) packer.write(hwia.to_h) end def read_hash_with_indifferent_access(unpacker) ActiveSupport::HashWithIndifferentAccess.new(unpacker.read) end def raise_unserializable(object, *) raise UnserializableObjectError, "Unsupported type #{object.class} for object #{object.inspect}" end def raise_invalid_format(*) raise "Invalid format" end def dump_class(klass) raise UnserializableObjectError, "Cannot serialize anonymous class" unless klass.name klass.name end def load_class(name) Object.const_get(name) rescue NameError => error if error.name.to_s == name raise MissingClassError, "Missing class: #{name}" else raise end end def write_class(klass, packer) packer.write(dump_class(klass)) end def read_class(unpacker) load_class(unpacker.read) end LOAD_WITH_MSGPACK_EXT = 0 LOAD_WITH_JSON_CREATE = 1 def write_object(object, packer) if object.class.respond_to?(:from_msgpack_ext) packer.write(LOAD_WITH_MSGPACK_EXT) write_class(object.class, packer) packer.write(object.to_msgpack_ext) elsif object.class.respond_to?(:json_create) packer.write(LOAD_WITH_JSON_CREATE) write_class(object.class, packer) packer.write(object.as_json) else raise_unserializable(object) end end def read_object(unpacker) case unpacker.read when LOAD_WITH_MSGPACK_EXT read_class(unpacker).from_msgpack_ext(unpacker.read) when LOAD_WITH_JSON_CREATE read_class(unpacker).json_create(unpacker.read) else raise_invalid_format end end end end end