• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ahamez / protox / 681e1c1651e9814e9fa76335ccafe68c8d26a635

31 Jan 2025 02:34PM UTC coverage: 93.99% (-0.02%) from 94.005%
681e1c1651e9814e9fa76335ccafe68c8d26a635

push

github

ahamez
style: flatten a little more the generated code

782 of 832 relevant lines covered (93.99%)

12957.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

95.77
/lib/protox/define_encoder.ex
1
defmodule Protox.DefineEncoder do
2
  @moduledoc false
3
  # Internal. Generates the encoder of a message.
4

5
  alias Protox.{Field, OneOf, Scalar}
6

7
  def define(fields, required_fields, syntax, opts \\ []) do
8
    %{oneofs: oneofs, proto3_optionals: proto3_optionals, others: fields_without_oneofs} =
90✔
9
      Protox.Defs.split_oneofs(fields)
10

11
    top_level_encode_fun =
90✔
12
      make_top_level_encode_fun(oneofs, proto3_optionals ++ fields_without_oneofs)
13

14
    encode_oneof_funs = make_encode_oneof_funs(oneofs)
90✔
15
    encode_field_funs = make_encode_field_funs(fields, required_fields, syntax)
90✔
16

17
    encode_unknown_fields_fun = make_encode_unknown_fields_fun(opts)
90✔
18

19
    quote do
20
      unquote(top_level_encode_fun)
21
      unquote_splicing(encode_oneof_funs)
22
      unquote_splicing(encode_field_funs)
23
      unquote(encode_unknown_fields_fun)
24
    end
25
  end
26

27
  defp make_top_level_encode_fun(oneofs, fields) do
28
    quote(do: [])
29
    |> make_encode_oneof_fun(oneofs)
30
    |> make_encode_fun_field(fields)
31
    |> make_encode_fun_body()
90✔
32
  end
33

34
  defp make_encode_fun_body(ast) do
35
    quote do
36
      @spec encode(struct()) :: {:ok, iodata()} | {:error, any()}
37
      def encode(msg) do
38
        try do
39
          {:ok, encode!(msg)}
40
        rescue
41
          e in [Protox.EncodingError, Protox.RequiredFieldsError] ->
42
            {:error, e}
43
        end
44
      end
45

46
      @spec encode!(struct()) :: iodata() | no_return()
47
      def encode!(msg), do: unquote(ast)
48
    end
49
  end
50

51
  defp make_encode_fun_field(ast, fields) do
52
    ast =
90✔
53
      Enum.reduce(fields, ast, fn %Protox.Field{} = field, ast_acc ->
54
        quote do
55
          unquote(ast_acc)
56
          |> unquote(make_encode_field_fun_name(field.name))(msg)
925✔
57
        end
58
      end)
59

60
    quote do
61
      unquote(ast) |> encode_unknown_fields(msg)
62
    end
63
  end
64

65
  defp make_encode_oneof_fun(ast, oneofs) do
66
    Enum.reduce(oneofs, ast, fn {parent_name, _children}, ast_acc ->
90✔
67
      quote do
68
        unquote(ast_acc)
69
        |> unquote(make_encode_field_fun_name(parent_name))(msg)
70
      end
71
    end)
72
  end
73

74
  defp make_encode_oneof_funs(oneofs) do
75
    for {parent_name, children} <- oneofs do
90✔
76
      nil_clause =
5✔
77
        quote do
78
          nil -> acc
79
        end
80

81
      children_clauses_ast =
5✔
82
        Enum.flat_map(children, fn %Field{} = child_field ->
83
          encode_child_fun_name = make_encode_field_fun_name(child_field.name)
50✔
84

85
          quote do
86
            {unquote(child_field.name), _field_value} -> unquote(encode_child_fun_name)(acc, msg)
50✔
87
          end
88
        end)
89

90
      quote do
91
        defp unquote(make_encode_field_fun_name(parent_name))(acc, msg) do
92
          case msg.unquote(parent_name) do
93
            unquote(nil_clause ++ children_clauses_ast)
94
          end
95
        end
96
      end
97
    end
98
  end
99

100
  defp make_encode_field_funs(fields, required_fields, syntax) do
101
    vars = %{
90✔
102
      acc: Macro.var(:acc, __MODULE__),
103
      msg: Macro.var(:msg, __MODULE__)
104
    }
105

106
    for %Field{name: name} = field <- fields do
90✔
107
      required = name in required_fields
975✔
108
      fun_name = make_encode_field_fun_name(name)
975✔
109
      fun_ast = make_encode_field_body(field, required, syntax, vars)
975✔
110

111
      quote do
112
        defp unquote(fun_name)(unquote(vars.acc), unquote(vars.msg)) do
975✔
113
          try do
114
            unquote(fun_ast)
115
          rescue
116
            ArgumentError ->
117
              reraise Protox.EncodingError.new(unquote(name), "invalid field value"),
118
                      __STACKTRACE__
119
          end
120
        end
121
      end
122
    end
123
  end
124

125
  defp make_encode_field_body(%Field{kind: %Scalar{}} = field, required, syntax, vars) do
126
    key = Protox.Encode.make_key_bytes(field.tag, field.type)
495✔
127
    var = quote do: unquote(vars.msg).unquote(field.name)
495✔
128
    encode_value_ast = get_encode_value_body(field.type, var)
495✔
129

130
    case syntax do
495✔
131
      :proto2 ->
132
        if required do
175✔
133
          quote do
134
            case unquote(vars.msg).unquote(field.name) do
10✔
135
              nil -> raise Protox.RequiredFieldsError.new([unquote(field.name)])
10✔
136
              _ -> [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
10✔
137
            end
138
          end
139
        else
140
          quote do
141
            case unquote(var) do
142
              nil -> unquote(vars.acc)
165✔
143
              _ -> [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
165✔
144
            end
145
          end
146
        end
147

148
      :proto3 ->
149
        quote do
150
          # Use == rather than pattern match for float comparison
151
          if unquote(var) == unquote(field.kind.default_value) do
320✔
152
            unquote(vars.acc)
320✔
153
          else
154
            [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
320✔
155
          end
156
        end
157
    end
158
  end
159

160
  # Generate the AST to encode child `field.name` of a oneof
161
  defp make_encode_field_body(
162
         %Field{kind: %OneOf{}} = field,
163
         _required,
164
         _syntax,
165
         vars
166
       ) do
167
    key = Protox.Encode.make_key_bytes(field.tag, field.type)
50✔
168
    var = Macro.var(:child_field_value, __MODULE__)
50✔
169
    encode_value_ast = get_encode_value_body(field.type, var)
50✔
170

171
    case field.label do
50✔
172
      :proto3_optional ->
173
        quote do
174
          case unquote(vars.msg).unquote(field.name) do
×
175
            nil ->
176
              [unquote(vars.acc)]
×
177

178
            unquote(var) ->
179
              [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
×
180
          end
181
        end
182

183
      _ ->
184
        # The dispatch on the correct child is performed by the parent encoding function,
185
        # this is why we don't check if the child is set.
186
        quote do
187
          {_, unquote(var)} = unquote(vars.msg).unquote(field.kind.parent)
50✔
188
          [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
50✔
189
        end
190
    end
191
  end
192

193
  defp make_encode_field_body(%Field{kind: :packed} = field, _required, _syntax, vars) do
194
    key = Protox.Encode.make_key_bytes(field.tag, :packed)
145✔
195
    encode_packed_ast = make_encode_packed_body(field.type)
145✔
196

197
    quote do
198
      case unquote(vars.msg).unquote(field.name) do
145✔
199
        [] -> unquote(vars.acc)
145✔
200
        values -> [unquote(vars.acc), unquote(key), unquote(encode_packed_ast)]
145✔
201
      end
202
    end
203
  end
204

205
  defp make_encode_field_body(%Field{kind: :unpacked} = field, _required, _syntax, vars) do
206
    encode_repeated_ast = make_encode_repeated_body(field.tag, field.type)
190✔
207

208
    quote do
209
      case unquote(vars.msg).unquote(field.name) do
190✔
210
        [] -> unquote(vars.acc)
190✔
211
        values -> [unquote(vars.acc), unquote(encode_repeated_ast)]
190✔
212
      end
213
    end
214
  end
215

216
  defp make_encode_field_body(%Field{kind: :map} = field, _required, _syntax, vars) do
217
    # Each key/value entry of a map has the same layout as a message.
218
    # https://developers.google.com/protocol-buffers/docs/proto3#backwards-compatibility
219

220
    key = Protox.Encode.make_key_bytes(field.tag, :map_entry)
95✔
221

222
    {map_key_type, map_value_type} = field.type
95✔
223

224
    k_var = Macro.var(:k, __MODULE__)
95✔
225
    v_var = Macro.var(:v, __MODULE__)
95✔
226

227
    encode_map_key_ast = get_encode_value_body(map_key_type, k_var)
95✔
228
    encode_map_value_ast = get_encode_value_body(map_value_type, v_var)
95✔
229

230
    map_key_key_bytes = Protox.Encode.make_key_bytes(1, map_key_type)
95✔
231
    map_value_key_bytes = Protox.Encode.make_key_bytes(2, map_value_type)
95✔
232
    map_keys_len = byte_size(map_value_key_bytes) + byte_size(map_key_key_bytes)
95✔
233

234
    quote do
235
      map = Map.fetch!(unquote(vars.msg), unquote(field.name))
95✔
236

237
      Enum.reduce(map, unquote(vars.acc), fn {unquote(k_var), unquote(v_var)},
95✔
238
                                             unquote(vars.acc) ->
95✔
239
        map_key_value_bytes = :binary.list_to_bin([unquote(encode_map_key_ast)])
240
        map_key_value_len = byte_size(map_key_value_bytes)
241

242
        map_value_value_bytes = :binary.list_to_bin([unquote(encode_map_value_ast)])
243
        map_value_value_len = byte_size(map_value_value_bytes)
244

245
        len =
246
          Protox.Varint.encode(unquote(map_keys_len) + map_key_value_len + map_value_value_len)
247

248
        [
249
          unquote(vars.acc),
95✔
250
          unquote(key),
251
          len,
252
          unquote(map_key_key_bytes),
253
          map_key_value_bytes,
254
          unquote(map_value_key_bytes),
255
          map_value_value_bytes
256
        ]
257
      end)
258
    end
259
  end
260

261
  defp make_encode_unknown_fields_fun(opts) do
262
    unknown_fields_name = Keyword.fetch!(opts, :unknown_fields_name)
90✔
263

264
    quote do
265
      defp encode_unknown_fields(acc, msg) do
266
        Enum.reduce(msg.unquote(unknown_fields_name), acc, fn {tag, wire_type, bytes}, acc ->
267
          case wire_type do
268
            0 ->
269
              [acc, Protox.Encode.make_key_bytes(tag, :int32), bytes]
270

271
            1 ->
272
              [acc, Protox.Encode.make_key_bytes(tag, :double), bytes]
273

274
            2 ->
275
              len_bytes = bytes |> byte_size() |> Protox.Varint.encode()
276
              [acc, Protox.Encode.make_key_bytes(tag, :packed), len_bytes, bytes]
277

278
            5 ->
279
              [acc, Protox.Encode.make_key_bytes(tag, :float), bytes]
280
          end
281
        end)
282
      end
283
    end
284
  end
285

286
  defp make_encode_packed_body(type) do
287
    value_var = Macro.var(:value, __MODULE__)
145✔
288
    encode_value_ast = get_encode_value_body(type, value_var)
145✔
289

290
    quote do
291
      {bytes, len} =
292
        Enum.reduce(values, {[], 0}, fn unquote(value_var), {acc, len} ->
293
          value_bytes = :binary.list_to_bin([unquote(encode_value_ast)])
294
          {[acc, value_bytes], len + byte_size(value_bytes)}
295
        end)
296

297
      [Protox.Varint.encode(len), bytes]
298
    end
299
  end
300

301
  defp make_encode_repeated_body(tag, type) do
302
    key = Protox.Encode.make_key_bytes(tag, type)
190✔
303
    value_var = Macro.var(:value, __MODULE__)
190✔
304
    encode_value_ast = get_encode_value_body(type, value_var)
190✔
305

306
    quote do
307
      Enum.reduce(values, [], fn unquote(value_var), acc ->
308
        [acc, unquote(key), unquote(encode_value_ast)]
309
      end)
310
    end
311
  end
312

313
  defp get_encode_value_body({:message, _}, value_var) do
314
    quote do
315
      Protox.Encode.encode_message(unquote(value_var))
316
    end
317
  end
318

319
  defp get_encode_value_body({:enum, enum}, value_var) do
320
    quote do
321
      unquote(value_var) |> unquote(enum).encode() |> Protox.Encode.encode_enum()
322
    end
323
  end
324

325
  defp get_encode_value_body(:bool, value_var) do
326
    quote(do: Protox.Encode.encode_bool(unquote(value_var)))
327
  end
328

329
  defp get_encode_value_body(:bytes, value_var) do
330
    quote(do: Protox.Encode.encode_bytes(unquote(value_var)))
331
  end
332

333
  defp get_encode_value_body(:string, value_var) do
334
    quote(do: Protox.Encode.encode_string(unquote(value_var)))
335
  end
336

337
  defp get_encode_value_body(:int32, value_var) do
338
    quote(do: Protox.Encode.encode_int32(unquote(value_var)))
339
  end
340

341
  defp get_encode_value_body(:int64, value_var) do
342
    quote(do: Protox.Encode.encode_int64(unquote(value_var)))
343
  end
344

345
  defp get_encode_value_body(:uint32, value_var) do
346
    quote(do: Protox.Encode.encode_uint32(unquote(value_var)))
347
  end
348

349
  defp get_encode_value_body(:uint64, value_var) do
350
    quote(do: Protox.Encode.encode_uint64(unquote(value_var)))
351
  end
352

353
  defp get_encode_value_body(:sint32, value_var) do
354
    quote(do: Protox.Encode.encode_sint32(unquote(value_var)))
355
  end
356

357
  defp get_encode_value_body(:sint64, value_var) do
358
    quote(do: Protox.Encode.encode_sint64(unquote(value_var)))
359
  end
360

361
  defp get_encode_value_body(:fixed32, value_var) do
362
    quote(do: Protox.Encode.encode_fixed32(unquote(value_var)))
363
  end
364

365
  defp get_encode_value_body(:fixed64, value_var) do
366
    quote(do: Protox.Encode.encode_fixed64(unquote(value_var)))
367
  end
368

369
  defp get_encode_value_body(:sfixed32, value_var) do
370
    quote(do: Protox.Encode.encode_sfixed32(unquote(value_var)))
371
  end
372

373
  defp get_encode_value_body(:sfixed64, value_var) do
374
    quote(do: Protox.Encode.encode_sfixed64(unquote(value_var)))
375
  end
376

377
  defp get_encode_value_body(:float, value_var) do
378
    quote(do: Protox.Encode.encode_float(unquote(value_var)))
379
  end
380

381
  defp get_encode_value_body(:double, value_var) do
382
    quote(do: Protox.Encode.encode_double(unquote(value_var)))
383
  end
384

385
  defp make_encode_field_fun_name(field) when is_atom(field) do
386
    String.to_atom("encode_#{field}")
1,960✔
387
  end
388
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc