# frozen_string_literal: true# typed: falsemoduleT::Props::SerializableincludeT::Props::Plugin# Required because we have special handling for `optional: false`includeT::Props::Optional# Required because we have special handling for extra_propsincludeT::Props::PrettyPrintable# Serializes this object to a hash, suitable for conversion to# JSON/BSON.## @param strict [T::Boolean] (true) If false, do not raise an# exception if this object has mandatory props with missing# values.# @return [Hash] A serialization of this object.defserialize(strict=true)h=__t_props_generated_serialize(strict)h.merge!(@_extra_props)if@_extra_propshendprivatedef__t_props_generated_serialize(strict)# No-op; will be overridden if there are any props.## To see the definition for class `Foo`, run `Foo.decorator.send(:generate_serialize_source)`{}end# Populates the property values on this object with the values# from a hash. In general, prefer to use {.from_hash} to construct# a new instance, instead of loading into an existing instance.## @param hash [Hash<String, Object>] The hash to take property# values from.# @param strict [T::Boolean] (false) If true, raise an exception if# the hash contains keys that do not correspond to any known# props on this instance.# @return [void]defdeserialize(hash,strict=false)hash_keys_matching_props=__t_props_generated_deserialize(hash)ifhash.size>hash_keys_matching_propsserialized_forms=self.class.decorator.prop_by_serialized_formsextra=hash.reject{|k,_|serialized_forms.key?(k)}# `extra` could still be empty here if the input matches a `dont_store` prop;# historically, we just ignore thoseif!extra.empty?ifstrictraise"Unknown properties for #{self.class.name}: #{extra.keys.inspect}"else@_extra_props=extraendendendendprivatedef__t_props_generated_deserialize(hash)# No-op; will be overridden if there are any props.## To see the definition for class `Foo`, run `Foo.decorator.send(:generate_deserialize_source)`0end# with() will clone the old object to the new object and merge the specified props to the new object.defwith(changed_props)with_existing_hash(changed_props,existing_hash: self.serialize)endprivatedefrecursive_stringify_keys(obj)ifobj.is_a?(Hash)new_obj=obj.class.newobj.eachdo|k,v|new_obj[k.to_s]=recursive_stringify_keys(v)endelsifobj.is_a?(Array)new_obj=obj.map{|v|recursive_stringify_keys(v)}elsenew_obj=objendnew_objendprivatedefwith_existing_hash(changed_props,existing_hash:)serialized=existing_hashnew_val=self.class.from_hash(serialized.merge(recursive_stringify_keys(changed_props)))old_extra=self.instance_variable_get(:@_extra_props)# rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccessnew_extra=new_val.instance_variable_get(:@_extra_props)# rubocop:disable PrisonGuard/NoLurkyInstanceVariableAccessifold_extra!=new_extradifference=ifold_extranew_extra.reject{|k,v|old_extra[k]==v}elsenew_extraendraiseArgumentError.new("Unexpected arguments: input(#{changed_props}), unexpected(#{difference})")endnew_valend# Asserts if this property is missing during strict serializeprivatedefrequired_prop_missing_from_serialize(prop)if@_required_props_missing_from_deserialize&.include?(prop)# If the prop was already missing during deserialization, that means the application# code already had to deal with a nil value, which means we wouldn't be accomplishing# much by raising here (other than causing an unnecessary breakage).T::Configuration.log_info_handler("chalk-odm: missing required property in serialize",prop: prop,class: self.class.name,id: self.class.decorator.get_id(self))elseraiseT::Props::InvalidValueError.new("#{self.class.name}.#{prop} not set for non-optional prop")endend# Marks this property as missing during deserializeprivatedefrequired_prop_missing_from_deserialize(prop)@_required_props_missing_from_deserialize||=Set[]@_required_props_missing_from_deserialize<<propnilendend############################################### NB: This must stay in the same file where T::Props::Serializable 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-L717moduleT::Props::Serializable::DecoratorMethodsincludeT::Props::HasLazilySpecializedMethods::DecoratorMethodsVALID_RULE_KEYS={dont_store: true,name: true,raise_on_nil_write: true}.freezeprivate_constant:VALID_RULE_KEYSdefvalid_rule_key?(key)super||VALID_RULE_KEYS[key]enddefrequired_props@class.props.select{|_,v|T::Props::Utils.required_prop?(v)}.keysenddefprop_dont_store?(prop);prop_rules(prop)[:dont_store];enddefprop_by_serialized_forms;@class.prop_by_serialized_forms;enddeffrom_hash(hash,strict=false)raiseArgumentError.new("#{hash.inspect} provided to from_hash")if!(hash&&hash.is_a?(Hash))i=@class.allocatei.deserialize(hash,strict)ienddefprop_serialized_form(prop)prop_rules(prop)[:serialized_form]enddefserialized_form_prop(serialized_form)prop_by_serialized_forms[serialized_form.to_s]||raise("No such serialized form: #{serialized_form.inspect}")enddefadd_prop_definition(prop,rules)rules[:serialized_form]=rules.fetch(:name,prop.to_s)res=superprop_by_serialized_forms[rules[:serialized_form]]=propenqueue_lazy_method_definition!(:__t_props_generated_serialize){generate_serialize_source}enqueue_lazy_method_definition!(:__t_props_generated_deserialize){generate_deserialize_source}resendprivatedefgenerate_serialize_sourceT::Props::Private::SerializerGenerator.generate(props)endprivatedefgenerate_deserialize_sourceT::Props::Private::DeserializerGenerator.generate(props,props_with_defaults||{},)enddefraise_nil_deserialize_error(hkey)msg="Tried to deserialize a required prop from a nil value. It's "\"possible that a nil value exists in the database, so you should "\"provide a `default: or factory:` for this prop (see go/optional "\"for more details). If this is already the case, you probably "\"omitted a required prop from the `fields:` option when doing a "\"partial load."storytime={prop: hkey,klass: decorated_class.name}# Notify the model owner if it exists, and always notify the API owner.beginifdefined?(Opus)&&defined?(Opus::Ownership)&&decorated_class<Opus::OwnershipT::Configuration.hard_assert_handler(msg,storytime: storytime,project: decorated_class.get_owner)endensureT::Configuration.hard_assert_handler(msg,storytime: storytime)endenddefprop_validate_definition!(name,cls,rules,type)result=superif(rules_name=rules[:name])unlessrules_name.is_a?(String)raiseArgumentError.new("Invalid name in prop #{@class.name}.#{name}: #{rules_name.inspect}")endvalidate_prop_name(rules_name)endif!rules[:raise_on_nil_write].nil?&&rules[:raise_on_nil_write]!=trueraiseArgumentError.new("The value of `raise_on_nil_write` if specified must be `true` (given: #{rules[:raise_on_nil_write]}).")endresultenddefget_id(instance)prop=prop_by_serialized_forms['_id']ifpropget(instance,prop)elsenilendendEMPTY_EXTRA_PROPS={}.freezeprivate_constant:EMPTY_EXTRA_PROPSdefextra_props(instance)instance.instance_variable_get(:@_extra_props)||EMPTY_EXTRA_PROPSend# @override T::Props::PrettyPrintableprivatedefinspect_instance_components(instance,multiline:,indent:)if(extra_props=extra_props(instance))&&!extra_props.empty?pretty_kvs=extra_props.map{|k,v|[k.to_sym,v.inspect]}extra=join_props_with_pretty_values(pretty_kvs,multiline: false)super+["@_extra_props=<#{extra}>"]elsesuperendendend############################################### NB: This must stay in the same file where T::Props::Serializable 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-L717moduleT::Props::Serializable::ClassMethodsdefprop_by_serialized_forms;@prop_by_serialized_forms||={};end# @!method self.from_hash(hash, strict)## Allocate a new instance and call {#deserialize} to load a new# object from a hash.# @return [Serializable]deffrom_hash(hash,strict=false)self.decorator.from_hash(hash,strict)end# Equivalent to {.from_hash} with `strict` set to true.# @return [Serializable]deffrom_hash!(hash)self.decorator.from_hash(hash,true)endend