Module: T::Props::Private::SerdeTransform

Extended by:
Sig
Defined in:
lib/types/props/private/serde_transform.rb

Defined Under Namespace

Modules: Mode

Class Method Summary collapse

Methods included from Sig

sig

Class Method Details

.generate(type, mode, varname) ⇒ Object



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/types/props/private/serde_transform.rb', line 36

def self.generate(type, mode, varname)
  case type
  when T::Types::TypedArray
    inner = generate(type.type, mode, 'v')
    if inner.nil?
      "#{varname}.dup"
    else
      "#{varname}.map {|v| #{inner}}"
    end
  when T::Types::TypedSet
    inner = generate(type.type, mode, 'v')
    if inner.nil?
      "#{varname}.dup"
    else
      "Set.new(#{varname}) {|v| #{inner}}"
    end
  when T::Types::TypedHash
    keys = generate(type.keys, mode, 'k')
    values = generate(type.values, mode, 'v')
    if keys && values
      "#{varname}.each_with_object({}) {|(k,v),h| h[#{keys}] = #{values}}"
    elsif keys
      "#{varname}.transform_keys {|k| #{keys}}"
    elsif values
      "#{varname}.transform_values {|v| #{values}}"
    else
      "#{varname}.dup"
    end
  when T::Types::Simple
    raw = type.raw_type
    if NO_TRANSFORM_TYPES.any? {|cls| raw <= cls}
      nil
    elsif raw <= Float
      case mode
      when Deserialize then "#{varname}.to_f"
      when Serialize then nil
      else T.absurd(mode)
      end
    elsif raw <= Numeric
      nil
    elsif raw < T::Props::Serializable
      handle_serializable_subtype(varname, raw, mode)
    elsif raw.singleton_class < T::Props::CustomType
      handle_custom_type(varname, T.unsafe(raw), mode)
    elsif T::Configuration.scalar_types.include?(raw.name)
      # It's a bit of a hack that this is separate from NO_TRANSFORM_TYPES
      # and doesn't check inheritance (like `T::Props::CustomType.scalar_type?`
      # does), but it covers the main use case (pay-server's custom `Boolean`
      # module) without either requiring `T::Configuration.scalar_types` to
      # accept modules instead of strings (which produces load-order issues
      # and subtle behavior changes) or eating the performance cost of doing
      # an inheritance check by manually crawling a class hierarchy and doing
      # string comparisons.
      nil
    else
      "T::Props::Utils.deep_clone_object(#{varname})"
    end
  when T::Types::Union
    non_nil_type = T::Utils.unwrap_nilable(type)
    if non_nil_type
      inner = generate(non_nil_type, mode, varname)
      if inner.nil?
        nil
      else
        "#{varname}.nil? ? nil : #{inner}"
      end
    elsif type.types.all? {|t| generate(t, mode, varname).nil?}
      # Handle, e.g., T::Boolean
      nil
    else
      # We currently deep_clone_object if the type was T.any(Integer, Float).
      # When we get better support for union types (maybe this specific
      # union type, because it would be a replacement for
      # Chalk::ODM::DeprecatedNumemric), we could opt to special case
      # this union to have no specific serde transform (the only reason
      # why Float has a special case is because round tripping through
      # JSON might normalize Floats to Integers)
      "T::Props::Utils.deep_clone_object(#{varname})"
    end
  when T::Types::Intersection
    dynamic_fallback = "T::Props::Utils.deep_clone_object(#{varname})"

    # Transformations for any members of the intersection type where we
    # know what we need to do and did not have to fall back to the
    # dynamic deep clone method.
    #
    # NB: This deliberately does include `nil`, which means we know we
    # don't need to do any transforming.
    inner_known = type.types
      .map {|t| generate(t, mode, varname)}
      .reject {|t| t == dynamic_fallback}
      .uniq

    if inner_known.size != 1
      # If there were no cases where we could tell what we need to do,
      # e.g. if this is `T.all(SomethingWeird, WhoKnows)`, just use the
      # dynamic fallback.
      #
      # If there were multiple cases and they weren't consistent, e.g.
      # if this is `T.all(String, T::Array[Integer])`, the type is probably
      # bogus/uninhabited, but use the dynamic fallback because we still
      # don't have a better option, and this isn't the place to raise that
      # error.
      dynamic_fallback
    else
      # This is probably something like `T.all(String, SomeMarker)` or
      # `T.all(SomeEnum, T.deprecated_enum(SomeEnum::FOO))` and we should
      # treat it like String or SomeEnum even if we don't know what to do
      # with the rest of the type.
      inner_known.first
    end
  when T::Types::Enum
    generate(T::Utils.lift_enum(type), mode, varname)
  else
    "T::Props::Utils.deep_clone_object(#{varname})"
  end
end