lib/types/props/private/serializer_generator.rb
# frozen_string_literal: true # typed: strict module T::Props module Private # Generates a specialized `serialize` implementation for a subclass of # T::Props::Serializable. # # The basic idea is that we analyze the props and for each prop, generate # the simplest possible logic as a block of Ruby source, so that we don't # pay the cost of supporting types like T:::Hash[CustomType, SubstructType] # when serializing a simple Integer. Then we join those together, # with a little shared logic to be able to detect when we get input keys # that don't match any prop. module SerializerGenerator extend T::Sig sig do params( props: T::Hash[Symbol, T::Hash[Symbol, T.untyped]], ) .returns(String) .checked(:never) end def self.generate(props) stored_props = props.reject {|_, rules| rules[:dont_store]} parts = stored_props.map do |prop, rules| # All of these strings should already be validated (directly or # indirectly) in `validate_prop_name`, so we don't bother with a nice # error message, but we double check here to prevent a refactoring # from introducing a security vulnerability. raise unless T::Props::Decorator::SAFE_NAME.match?(prop.to_s) hash_key = rules.fetch(:serialized_form) raise unless T::Props::Decorator::SAFE_NAME.match?(hash_key) ivar_name = rules.fetch(:accessor_key).to_s raise unless ivar_name.start_with?('@') && T::Props::Decorator::SAFE_NAME.match?(ivar_name[1..-1]) transformed_val = SerdeTransform.generate( T::Utils::Nilable.get_underlying_type_object(rules.fetch(:type_object)), SerdeTransform::Mode::SERIALIZE, ivar_name ) || ivar_name nil_asserter = if rules[:fully_optional] '' else "required_prop_missing_from_serialize(#{prop.inspect}) if strict" end # Don't serialize values that are nil to save space (both the # nil value itself and the field name in the serialized BSON # document) <<~RUBY if #{ivar_name}.nil? #{nil_asserter} else h[#{hash_key.inspect}] = #{transformed_val} end RUBY end <<~RUBY def __t_props_generated_serialize(strict) h = {} #{parts.join("\n\n")} h end RUBY end end end end