# frozen_string_literal: true module LIT # @api public # @since 0.1.0 class RequestDeserializer AST = Parser::AST def initialize(raw_request, type_root) # { action, payload } @raw_request = raw_request @type_root = type_root end def deserialize_request action_namespace = @raw_request.fetch("action").split(".").map { |x| Utils.camelize(x) } action_type = action_namespace.reduce(@type_root) do |object, namespace| object.const_get(namespace) end request_type = action_type.const_get("Request") @type_module = action_type.const_get("DefinedIn") deserialize(request_type, @raw_request.fetch("payload")) end private # rubocop:disable Metrics/MethodLength def deserialize(type, payload) if type.is_a?(Module) if type <= Object::Struct deserialize_struct(type, payload) elsif type <= Object::Enum deserialize_enum(type, payload) elsif type == AST::Type::Primitive::String || type == AST::Type::Primitive::Integer || type == AST::Type::Primitive::Float || type == AST::Type::Primitive::Boolean || type == AST::Type::Primitive::Unit payload else deserialize(type::TYPE, payload) end elsif type.is_a?(AST::Type::Alias) deserialize(@type_module.const_get(type.name), payload) elsif type.is_a?(AST::Type::Primitive::Array) deserialize_array(type, payload) elsif type.is_a?(AST::Type::Primitive::Map) deserialize_map(type, payload) elsif type.is_a?(AST::Type::Primitive::Option) deserialize_option(type.type, payload) else raise InvalidTypeError, "invalid type: #{type}" end end # rubocop:enable Metrics/MethodLength def deserialize_struct(struct_klass, struct_data) params = struct_data.reduce({}) do |acc, (key, value)| field_name = key.to_sym field_type = struct_klass.__fields__[field_name] acc.merge(field_name => deserialize(field_type, value)) end struct_klass.new(params) end def deserialize_enum(enum_module, enum_data) variant_name = Utils.camelize(enum_data.fetch("kind")) variant = enum_module.const_get(variant_name) if variant.__kind__ == :unit variant elsif variant.__kind__ == :struct deserialize_struct(variant, enum_data.fetch("data")) end end def deserialize_option(type, data) if data.nil? Object::Option::None else deserialized_data = deserialize(type, data) option = Builder::Option.new(@type_module, type).build option::Some.new(deserialized_data) end end def deserialize_map(type, data) map = Builder::Map.new(@type_module, type.key_type, type.value_type).build deserialized_data = data.reduce({}) do |acc, (key, value)| acc.merge(deserialize(type.key_type, key) => deserialize(type.value_type, value)) end map.new(deserialized_data) end def deserialize_array(type, data) array = Builder::Array.new(@type_module, type.type).build array.new(*data.map { |x| deserialize(type.type, x) }) end end end