app/models/coupdoeil/params.rb



# frozen_string_literal: true

module Coupdoeil
  module Params
    extend self

    GLOBAL_ID_KEY = "_cd_globalid"

    RESERVED_KEYS = [GLOBAL_ID_KEY, GLOBAL_ID_KEY.to_sym].freeze

    private_constant :RESERVED_KEYS, :GLOBAL_ID_KEY

    SerializationError = Class.new(StandardError)

    def deserialize_global_id(hash) = GlobalID::Locator.locate hash[GLOBAL_ID_KEY]
    def deserialize_hash(serialized_hash) = serialized_hash.transform_values { deserialize_param(_1) }
    def serialized_global_id?(hash) = hash.size == 1 && hash.include?(GLOBAL_ID_KEY)

    def serialize(*params)
      ActiveSupport::Notifications.instrument("serialize_hovercard_params.coupdoeil") do
        params.map { |param| serialize_param(param) }
      end
    end

    def deserialize(params)
      Array.wrap(params).map { |param| deserialize_param(param) }
    rescue StandardError
      raise DeserializationError
    end

    def serialize_param(param)
      case param
      when GlobalID::Identification
        convert_to_global_id_hash(param)
      when Array
        param.map { serialize_param(_1) }
      when Hash
        serialize_hash(param)
      else
        param
      end
    end

    def serialize_hash(param)
      param.each_with_object({}) do |(key, value), hash|
        hash[serialize_hash_key(key)] = serialize_param(value)
      end
    end

    def serialize_hash_key(key)
      case key
      when *RESERVED_KEYS
        raise Coupdoeil::SerializationError, "Can't serialize a Hash with reserved key #{key.inspect}"
      when String, Symbol
        key.to_s
      else
        raise Coupdoeil::SerializationError, "Only string and symbol hash keys may be serialized as hovercard params, but #{key.inspect} is a #{key.class}"
      end
    end

    def convert_to_global_id_hash(param)
      { GLOBAL_ID_KEY => param.to_global_id.to_s }
    rescue URI::GID::MissingModelIdError
      raise Coupdoeil::SerializationError, "Unable to serialize #{param.class} " \
        "without an id. (Maybe you forgot to call save?)"
    end

    def deserialize_param(param)
      case param
      when Array
        param.map { |arg| deserialize_param(arg) }
      when Hash
        if serialized_global_id?(param)
          deserialize_global_id param
        else
          deserialize_hash(param)
        end
      else
        param
      end
    end
  end
end