lib/types/props/optional.rb



# frozen_string_literal: true
# typed: false

module T::Props::Optional
  include T::Props::Plugin
end


##############################################


# NB: This must stay in the same file where T::Props::Optional is defined due to
# T::Props::Decorator#apply_plugin; see https://git.corp.stripe.com/stripe-internal/pay-server/blob/fc7f15593b49875f2d0499ffecfd19798bac05b3/chalk/odm/lib/chalk-odm/document_decorator.rb#L716-L717
module T::Props::Optional::DecoratorMethods
  # TODO: clean this up. This set of options is confusing, and some of them are not universally
  # applicable (e.g., :on_load only applies when using T::Serializable).
  VALID_OPTIONAL_RULES = Set[
    :existing, # deprecated
    :on_load,
    false,
    true,
  ].freeze

  def valid_props
    super + [
      :default,
      :factory,
      :optional,
    ]
  end

  def prop_optional?(prop); prop_rules(prop)[:fully_optional]; end

  def mutate_prop_backdoor!(prop, key, value)
    rules = props.fetch(prop)
    rules = rules.merge(key => value)
    compute_derived_rules(rules)
    @props = props.merge(prop => rules.freeze).freeze
  end

  def compute_derived_rules(rules)
    rules[:fully_optional] = !T::Props::Utils.need_nil_write_check?(rules)
    rules[:need_nil_read_check] = T::Props::Utils.need_nil_read_check?(rules)
  end

  def add_prop_definition(prop, rules)
    compute_derived_rules(rules)
    super
  end

  def prop_validate_definition!(name, cls, rules, type)
    result = super

    if (rules_optional = rules[:optional])
      if !VALID_OPTIONAL_RULES.include?(rules_optional)
        raise ArgumentError.new(":optional must be one of #{VALID_OPTIONAL_RULES.inspect}")
      end
    end

    if rules.key?(:default) && rules.key?(:factory)
      raise ArgumentError.new("Setting both :default and :factory is invalid. See: go/chalk-docs")
    end

    result
  end

  def has_default?(rules)
    rules.include?(:default) || rules.include?(:factory)
  end

  def get_default(rules, instance_class)
    if rules.include?(:default)
      default = rules[:default]
      T::Props::Utils.deep_clone_object(default)
    elsif rules.include?(:factory)
      # Factory should never be nil if the key is specified, but
      # we do this rather than 'elsif rules[:factory]' for
      # consistency with :default.
      factory = rules[:factory]
      instance_class.class_exec(&factory)
    else
      nil
    end
  end
end