def prop_defined(name, cls, rules={})
def prop_defined(name, cls, rules={})
if rules[:optional] == true
T::Configuration.hard_assert_handler(
'Use of `optional: true` is deprecated, please use `T.nilable(...)` instead.',
storytime: {
name: name,
cls_or_args: cls.to_s,
args: rules,
klass: decorated_class.name,
},
)
elsif rules[:optional] == false
T::Configuration.hard_assert_handler(
'Use of `optional: :false` is deprecated as it\'s the default value.',
storytime: {
name: name,
cls_or_args: cls.to_s,
args: rules,
klass: decorated_class.name,
},
)
elsif rules[:optional] == :on_load
T::Configuration.hard_assert_handler(
'Use of `optional: :on_load` is deprecated. You probably want `T.nilable(...)` with :raise_on_nil_write instead.',
storytime: {
name: name,
cls_or_args: cls.to_s,
args: rules,
klass: decorated_class.name,
},
)
elsif rules[:optional] == :existing
T::Configuration.hard_assert_handler(
'Use of `optional: :existing` is not allowed: you should use use T.nilable (http://go/optional)',
storytime: {
name: name,
cls_or_args: cls.to_s,
args: rules,
klass: decorated_class.name,
},
)
end
if T::Utils::Nilable.is_union_with_nilclass(cls)
# :_tnilable is introduced internally for performance purpose so that clients do not need to call
# T::Utils::Nilable.is_tnilable(cls) again.
# It is strictly internal: clients should always use T::Props::Utils.required_prop?() or
# T::Props::Utils.optional_prop?() for checking whether a field is required or optional.
rules[:_tnilable] = true
end
name = name.to_sym
type = cls
if !cls.is_a?(Module)
cls = convert_type_to_class(cls)
end
type_object = type
if !(type_object.singleton_class < T::Props::CustomType)
type_object = smart_coerce(type_object, array: rules[:array], enum: rules[:enum])
end
prop_validate_definition!(name, cls, rules, type_object)
# Retrive the possible underlying object with T.nilable.
underlying_type_object = T::Utils::Nilable.get_underlying_type_object(type_object)
type = T::Utils::Nilable.get_underlying_type(type)
array_subdoc_type = array_subdoc_type(underlying_type_object)
hash_value_subdoc_type = hash_value_subdoc_type(underlying_type_object)
hash_key_custom_type = hash_key_custom_type(underlying_type_object)
sensitivity_and_pii = {sensitivity: rules[:sensitivity]}
if defined?(Opus) && defined?(Opus::Sensitivity) && defined?(Opus::Sensitivity::Utils)
sensitivity_and_pii = Opus::Sensitivity::Utils.normalize_sensitivity_and_pii_annotation(sensitivity_and_pii)
end
# We check for Class so this is only applied on concrete
# documents/models; We allow mixins containing props to not
# specify their PII nature, as long as every class into which they
# are ultimately included does.
#
if defined?(Opus) && defined?(Opus::Sensitivity) && defined?(Opus::Sensitivity::PIIable)
if sensitivity_and_pii[:pii] && @class.is_a?(Class) && !@class.contains_pii?
raise ArgumentError.new(
'Cannot include a pii prop in a class that declares `contains_no_pii`'
)
end
end
needs_clone =
if cls <= Array || cls <= Hash || cls <= Set
shallow_clone_ok(underlying_type_object) ? :shallow : true
else
false
end
rules = rules.merge(
# TODO: The type of this element is confusing. We should refactor so that
# it can be always `type_object` (a PropType) or always `cls` (a Module)
type: type,
# These are precomputed for performance
# TODO: A lot of these are only needed by T::Props::Serializable or T::Struct
# and can/should be moved accordingly.
type_is_custom_type: cls.singleton_class < T::Props::CustomType,
type_is_serializable: cls < T::Props::Serializable,
type_is_array_of_serializable: !array_subdoc_type.nil?,
type_is_hash_of_serializable_values: !hash_value_subdoc_type.nil?,
type_is_hash_of_custom_type_keys: !hash_key_custom_type.nil?,
type_object: type_object,
type_needs_clone: needs_clone,
accessor_key: "@#{name}".to_sym,
sensitivity: sensitivity_and_pii[:sensitivity],
pii: sensitivity_and_pii[:pii],
# extra arbitrary metadata attached by the code defining this property
extra: rules[:extra]&.freeze,
)
validate_not_missing_sensitivity(name, rules)
# for backcompat
if type.is_a?(T::Types::TypedArray) && type.type.is_a?(T::Types::Simple)
rules[:array] = type.type.raw_type
elsif array_subdoc_type
rules[:array] = array_subdoc_type
end
if rules[:type_is_serializable]
rules[:serializable_subtype] = cls
elsif array_subdoc_type
rules[:serializable_subtype] = array_subdoc_type
elsif hash_value_subdoc_type && hash_key_custom_type
rules[:serializable_subtype] = {
keys: hash_key_custom_type,
values: hash_value_subdoc_type,
}
elsif hash_value_subdoc_type
rules[:serializable_subtype] = hash_value_subdoc_type
elsif hash_key_custom_type
rules[:serializable_subtype] = hash_key_custom_type
end
add_prop_definition(name, rules)
# NB: using `without_accessors` doesn't make much sense unless you also define some other way to
# get at the property (e.g., Chalk::ODM::Document exposes `get` and `set`).
define_getter_and_setter(name, rules) unless rules[:without_accessors]
if rules[:foreign] && rules[:foreign_hint_only]
raise ArgumentError.new(":foreign and :foreign_hint_only are mutually exclusive.")
end
handle_foreign_option(name, cls, rules, rules[:foreign]) if rules[:foreign]
handle_foreign_hint_only_option(cls, rules[:foreign_hint_only]) if rules[:foreign_hint_only]
handle_redaction_option(name, rules[:redaction]) if rules[:redaction]
end