lib/types/props/custom_type.rb
# frozen_string_literal: true # typed: false module T::Props module CustomType include Kernel # for `is_a?` # Returns true if the given Ruby value can be assigned to a T::Props field # of this type. # # @param [Object] _value # @return T::Boolean def instance?(_value) raise NotImplementedError.new('Must override in included class') end # Alias for consistent interface with T::Types::Base def valid?(value) instance?(value) end # Given an instance of this type, serialize that into a scalar type # supported by T::Props. # # @param [Object] _instance # @return An instance of one of T::Configuration.scalar_types def serialize(_instance) raise NotImplementedError.new('Must override in included class') end # Given the serialized form of your type, this returns an instance # of that custom type representing that value. # # @param _mongo_scalar One of T::Configuration.scalar_types # @return Object def deserialize(_mongo_scalar) raise NotImplementedError.new('Must override in included class') end def self.included(_base) super raise 'Please use "extend", not "include" to attach this module' end def self.scalar_type?(val) # We don't need to check for val's included modules in # T::Configuration.scalar_types, because T::Configuration.scalar_types # are all classes. klass = val.class until klass.nil? return true if T::Configuration.scalar_types.include?(klass.to_s) klass = klass.superclass end false end # We allow custom types to serialize to Arrays, so that we can # implement set-like fields that store a unique-array, but forbid # hashes; Custom hash types should be implemented via an emebdded # T::Struct (or a subclass like Chalk::ODM::Document) or via T. def self.valid_serialization?(val, type=nil) if type&.name == 'Chalk::ODM::BsonTypes::BsonObject' # Special case we allow for backwards compatibility with props formerly # typed as "Object" or "Hash", which contain arbitrarily-nested BSON # data (e.g. parsed API request bodies). In general, we aren't pushing # to convert these to Chalk::ODM::BsonTypes - we'd rather delurk them - # but this lets us convert events with these types to Proto. return true end case val when Array val.each do |v| return false unless scalar_type?(v) end true else scalar_type?(val) end end def self.checked_serialize(type, instance) val = type.serialize(instance) unless valid_serialization?(val, type) msg = "#{type} did not serialize to a valid scalar type. It became a: #{val.class}" if val.is_a?(Hash) msg += "\nIf you want to store a structured Hash, consider using a T::Struct as your type." end raise T::Props::InvalidValueError.new(msg) end val end end end