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

ahamez / protox / c416603a46de49dbd993f6c99a6ce36f18a46860

09 Feb 2025 05:25PM UTC coverage: 94.959% (-0.01%) from 94.971%
c416603a46de49dbd993f6c99a6ce36f18a46860

push

github

ahamez
test: remove redundant proto definition

810 of 853 relevant lines covered (94.96%)

12662.17 hits per line

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

95.56
/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, syntax, opts \\ []) do
8
    vars = %{
90✔
9
      acc: Macro.var(:acc, __MODULE__),
10
      acc_size: Macro.var(:acc_size, __MODULE__),
11
      msg: Macro.var(:msg, __MODULE__)
12
    }
13

14
    required_fields = get_required_fields(fields)
90✔
15

16
    %{oneofs: oneofs, proto3_optionals: proto3_optionals, others: fields_without_oneofs} =
90✔
17
      Protox.Defs.split_oneofs(fields)
18

19
    top_level_encode_fun =
90✔
20
      make_top_level_encode_fun(oneofs, proto3_optionals ++ fields_without_oneofs)
21

22
    encode_oneof_funs = make_encode_oneof_funs(oneofs)
90✔
23
    encode_field_funs = make_encode_field_funs(fields, required_fields, syntax, vars)
90✔
24
    encode_unknown_fields_fun = make_encode_unknown_fields_fun(vars, opts)
90✔
25

26
    quote do
27
      unquote(top_level_encode_fun)
28
      unquote_splicing(encode_oneof_funs)
29
      unquote_splicing(encode_field_funs)
30
      unquote(encode_unknown_fields_fun)
31
    end
32
  end
33

34
  defp make_top_level_encode_fun(oneofs, fields) do
35
    quote(do: {_acc = [], _acc_size = 0})
36
    |> make_encode_oneof_fun(oneofs)
37
    |> make_encode_fun_field(fields)
38
    |> make_encode_fun_body()
90✔
39
  end
40

41
  defp make_encode_fun_body(ast) do
42
    quote do
43
      @spec encode(t()) :: {:ok, iodata(), non_neg_integer()} | {:error, any()}
44
      def encode(msg) do
45
        try do
46
          msg |> encode!() |> Tuple.insert_at(0, :ok)
47
        rescue
48
          e in [Protox.EncodingError, Protox.RequiredFieldsError] ->
49
            {:error, e}
50
        end
51
      end
52

53
      @spec encode!(t()) :: {iodata(), non_neg_integer()} | no_return()
54
      def encode!(msg), do: unquote(ast)
55
    end
56
  end
57

58
  defp make_encode_fun_field(ast, fields) do
59
    ast =
90✔
60
      Enum.reduce(fields, ast, fn %Protox.Field{} = field, ast_acc ->
61
        quote do
62
          unquote(ast_acc)
63
          |> unquote(make_encode_field_fun_name(field.name))(msg)
925✔
64
        end
65
      end)
66

67
    quote do
68
      unquote(ast) |> encode_unknown_fields(msg)
69
    end
70
  end
71

72
  defp make_encode_oneof_fun(ast, oneofs) do
73
    Enum.reduce(oneofs, ast, fn {parent_name, _children}, ast_acc ->
90✔
74
      quote do
75
        unquote(ast_acc)
76
        |> unquote(make_encode_field_fun_name(parent_name))(msg)
77
      end
78
    end)
79
  end
80

81
  defp make_encode_oneof_funs(oneofs) do
82
    for {parent_name, children} <- oneofs do
90✔
83
      nil_clause =
5✔
84
        quote do
85
          nil -> acc
86
        end
87

88
      children_clauses_ast =
5✔
89
        Enum.flat_map(children, fn %Field{} = child_field ->
90
          encode_child_fun_name = make_encode_field_fun_name(child_field.name)
50✔
91

92
          quote do
93
            {unquote(child_field.name), _field_value} -> unquote(encode_child_fun_name)(acc, msg)
50✔
94
          end
95
        end)
96

97
      quote do
98
        defp unquote(make_encode_field_fun_name(parent_name))(acc, msg) do
99
          case msg.unquote(parent_name) do
100
            unquote(nil_clause ++ children_clauses_ast)
101
          end
102
        end
103
      end
104
    end
105
  end
106

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

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

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

132
    encode_value_clause =
495✔
133
      quote do
134
        {value_bytes, value_bytes_size} = unquote(encode_value_ast)
135

136
        {
137
          [unquote(key), value_bytes | unquote(vars.acc)],
495✔
138
          unquote(vars.acc_size) + unquote(key_size) + value_bytes_size
495✔
139
        }
140
      end
141

142
    case {syntax, required} do
495✔
143
      {:proto2, _required = true} ->
144
        quote do
145
          case unquote(vars.msg).unquote(field.name) do
10✔
146
            nil -> raise Protox.RequiredFieldsError.new([unquote(field.name)])
10✔
147
            _ -> unquote(encode_value_clause)
148
          end
149
        end
150

151
      {:proto2, _required = false} ->
152
        quote do
153
          case unquote(var) do
154
            nil -> {unquote(vars.acc), unquote(vars.acc_size)}
165✔
155
            _ -> unquote(encode_value_clause)
156
          end
157
        end
158

159
      {:proto3, _required} ->
160
        quote do
161
          # Use == rather than pattern match for float comparison
162
          if unquote(var) == unquote(field.kind.default_value) do
320✔
163
            {unquote(vars.acc), unquote(vars.acc_size)}
320✔
164
          else
165
            unquote(encode_value_clause)
166
          end
167
        end
168
    end
169
  end
170

171
  # Generate the AST to encode child `field.name` of a oneof
172
  defp make_encode_field_body(
173
         %Field{kind: %OneOf{}} = field,
174
         _required,
175
         _syntax,
176
         vars
177
       ) do
178
    {key, key_size} = Protox.Encode.make_key_bytes(field.tag, field.type)
50✔
179
    var = Macro.var(:child_field_value, __MODULE__)
50✔
180
    encode_value_ast = get_encode_value_body(field.type, var)
50✔
181

182
    case field.label do
50✔
183
      :proto3_optional ->
184
        quote do
185
          case unquote(vars.msg).unquote(field.name) do
×
186
            nil ->
187
              {unquote(vars.acc), unquote(vars.acc_size)}
×
188

189
            unquote(var) ->
190
              {value_bytes, value_bytes_size} = unquote(encode_value_ast)
191

192
              {
193
                [unquote(key), value_bytes | unquote(vars.acc)],
×
194
                unquote(vars.acc_size) + unquote(key_size) + value_bytes_size
×
195
              }
196
          end
197
        end
198

199
      _ ->
200
        # The dispatch on the correct child is performed by the parent encoding function,
201
        # this is why we don't check if the child is set.
202
        quote do
203
          {_, unquote(var)} = unquote(vars.msg).unquote(field.kind.parent)
50✔
204
          {value_bytes, value_bytes_size} = unquote(encode_value_ast)
205

206
          {
207
            [unquote(key), value_bytes | unquote(vars.acc)],
50✔
208
            unquote(vars.acc_size) + unquote(key_size) + value_bytes_size
50✔
209
          }
210
        end
211
    end
212
  end
213

214
  defp make_encode_field_body(%Field{kind: :packed} = field, _required, _syntax, vars) do
215
    {key_bytes, key_size} = Protox.Encode.make_key_bytes(field.tag, :packed)
145✔
216
    encode_packed_ast = make_encode_packed_body(field.type)
145✔
217

218
    quote do
219
      case unquote(vars.msg).unquote(field.name) do
145✔
220
        [] ->
221
          {unquote(vars.acc), unquote(vars.acc_size)}
145✔
222

223
        values ->
224
          {packed_bytes, packed_size} = unquote(encode_packed_ast)
225

226
          {
227
            [unquote(key_bytes), packed_bytes | unquote(vars.acc)],
145✔
228
            unquote(vars.acc_size) + unquote(key_size) + packed_size
145✔
229
          }
230
      end
231
    end
232
  end
233

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

237
    quote do
238
      case unquote(vars.msg).unquote(field.name) do
190✔
239
        [] ->
240
          {unquote(vars.acc), unquote(vars.acc_size)}
190✔
241

242
        values ->
243
          {value_bytes, value_size} = unquote(encode_repeated_ast)
244
          {[value_bytes | unquote(vars.acc)], unquote(vars.acc_size) + value_size}
190✔
245
      end
246
    end
247
  end
248

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

253
    {field_key, field_key_size} = Protox.Encode.make_key_bytes(field.tag, :map_entry)
95✔
254

255
    {map_key_type, map_value_type} = field.type
95✔
256

257
    k_var = Macro.var(:k, __MODULE__)
95✔
258
    v_var = Macro.var(:v, __MODULE__)
95✔
259

260
    encode_map_key_ast = get_encode_value_body(map_key_type, k_var)
95✔
261
    encode_map_value_ast = get_encode_value_body(map_value_type, v_var)
95✔
262

263
    {k_key_bytes, k_key_size} = Protox.Encode.make_key_bytes(1, map_key_type)
95✔
264
    {v_key_bytes, v_key_size} = Protox.Encode.make_key_bytes(2, map_value_type)
95✔
265
    keys_len = k_key_size + v_key_size
95✔
266

267
    quote do
268
      map = Map.fetch!(unquote(vars.msg), unquote(field.name))
95✔
269

270
      if map_size(map) == 0 do
271
        {unquote(vars.acc), unquote(vars.acc_size)}
95✔
272
      else
273
        Enum.reduce(
274
          map,
275
          {unquote(vars.acc), unquote(vars.acc_size)},
95✔
276
          fn {unquote(k_var), unquote(v_var)}, {unquote(vars.acc), unquote(vars.acc_size)} ->
95✔
277
            {k_value_bytes, k_value_len} = unquote(encode_map_key_ast)
278
            {v_value_bytes, v_value_len} = unquote(encode_map_value_ast)
279

280
            len = unquote(keys_len) + k_value_len + v_value_len
281
            {len_varint, len_varint_size} = Protox.Varint.encode(len)
282

283
            unquote(vars.acc) = [
95✔
284
              <<unquote(field_key), len_varint::binary, unquote(k_key_bytes)>>,
285
              k_value_bytes,
286
              unquote(v_key_bytes),
287
              v_value_bytes
288
              | unquote(vars.acc)
95✔
289
            ]
290

291
            {
292
              unquote(vars.acc),
95✔
293
              unquote(vars.acc_size) + unquote(field_key_size + keys_len) + k_value_len +
95✔
294
                v_value_len + len_varint_size
295
            }
296
          end
297
        )
298
      end
299
    end
300
  end
301

302
  defp make_encode_unknown_fields_fun(vars, opts) do
303
    unknown_fields_name = Keyword.fetch!(opts, :unknown_fields_name)
90✔
304

305
    quote do
306
      defp encode_unknown_fields({unquote(vars.acc), unquote(vars.acc_size)}, msg) do
90✔
307
        Enum.reduce(
308
          msg.unquote(unknown_fields_name),
309
          {unquote(vars.acc), unquote(vars.acc_size)},
90✔
310
          fn {tag, wire_type, bytes}, {unquote(vars.acc), unquote(vars.acc_size)} ->
90✔
311
            case wire_type do
312
              0 ->
313
                {key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :int32)
314

315
                {
316
                  [unquote(vars.acc), <<key_bytes::binary, bytes::binary>>],
90✔
317
                  unquote(vars.acc_size) + key_size + byte_size(bytes)
90✔
318
                }
319

320
              1 ->
321
                {key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :double)
322

323
                {
324
                  [unquote(vars.acc), <<key_bytes::binary, bytes::binary>>],
90✔
325
                  unquote(vars.acc_size) + key_size + byte_size(bytes)
90✔
326
                }
327

328
              2 ->
329
                {len_bytes, len_size} = bytes |> byte_size() |> Protox.Varint.encode()
330
                {key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :packed)
331

332
                {
333
                  [unquote(vars.acc), <<key_bytes::binary, len_bytes::binary, bytes::binary>>],
90✔
334
                  unquote(vars.acc_size) + key_size + len_size + byte_size(bytes)
90✔
335
                }
336

337
              5 ->
338
                {key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :float)
339

340
                {
341
                  [unquote(vars.acc), <<key_bytes::binary, bytes::binary>>],
90✔
342
                  unquote(vars.acc_size) + key_size + byte_size(bytes)
90✔
343
                }
344
            end
345
          end
346
        )
347
      end
348
    end
349
  end
350

351
  defp make_encode_packed_body(type) do
352
    value_var = Macro.var(:value, __MODULE__)
145✔
353
    encode_value_ast = get_encode_value_body(type, value_var)
145✔
354

355
    quote do
356
      {value_bytes, value_size} =
357
        Enum.reduce(
358
          values,
359
          {_local_acc = [], _local_acc_size = 0},
360
          fn unquote(value_var), {local_acc, local_acc_size} ->
361
            {value_bytes, value_bytes_size} = unquote(encode_value_ast)
362

363
            {
364
              [value_bytes | local_acc],
365
              local_acc_size + value_bytes_size
366
            }
367
          end
368
        )
369

370
      {value_size_bytes, value_size_size} = Protox.Varint.encode(value_size)
371

372
      {[value_size_bytes, Enum.reverse(value_bytes)], value_size + value_size_size}
373
    end
374
  end
375

376
  defp make_encode_repeated_body(tag, type) do
377
    {key_bytes, key_bytes_sz} = Protox.Encode.make_key_bytes(tag, type)
190✔
378
    value_var = Macro.var(:value, __MODULE__)
190✔
379
    encode_value_ast = get_encode_value_body(type, value_var)
190✔
380

381
    quote do
382
      {value_bytes, value_size} =
383
        Enum.reduce(
384
          values,
385
          {_local_acc = [], _local_acc_size = 0},
386
          fn unquote(value_var), {local_acc, local_acc_size} ->
387
            {value_bytes, value_bytes_size} = unquote(encode_value_ast)
388

389
            {
390
              [value_bytes, unquote(key_bytes) | local_acc],
391
              local_acc_size + unquote(key_bytes_sz) + value_bytes_size
392
            }
393
          end
394
        )
395

396
      {Enum.reverse(value_bytes), value_size}
397
    end
398
  end
399

400
  defp get_encode_value_body({:message, _}, value_var) do
401
    quote do
402
      Protox.Encode.encode_message(unquote(value_var))
403
    end
404
  end
405

406
  defp get_encode_value_body({:enum, enum}, value_var) do
407
    quote do
408
      unquote(value_var) |> unquote(enum).encode() |> Protox.Encode.encode_enum()
409
    end
410
  end
411

412
  defp get_encode_value_body(:bool, value_var) do
413
    quote(do: Protox.Encode.encode_bool(unquote(value_var)))
414
  end
415

416
  defp get_encode_value_body(:bytes, value_var) do
417
    quote(do: Protox.Encode.encode_bytes(unquote(value_var)))
418
  end
419

420
  defp get_encode_value_body(:string, value_var) do
421
    quote(do: Protox.Encode.encode_string(unquote(value_var)))
422
  end
423

424
  defp get_encode_value_body(:int32, value_var) do
425
    quote(do: Protox.Encode.encode_int32(unquote(value_var)))
426
  end
427

428
  defp get_encode_value_body(:int64, value_var) do
429
    quote(do: Protox.Encode.encode_int64(unquote(value_var)))
430
  end
431

432
  defp get_encode_value_body(:uint32, value_var) do
433
    quote(do: Protox.Encode.encode_uint32(unquote(value_var)))
434
  end
435

436
  defp get_encode_value_body(:uint64, value_var) do
437
    quote(do: Protox.Encode.encode_uint64(unquote(value_var)))
438
  end
439

440
  defp get_encode_value_body(:sint32, value_var) do
441
    quote(do: Protox.Encode.encode_sint32(unquote(value_var)))
442
  end
443

444
  defp get_encode_value_body(:sint64, value_var) do
445
    quote(do: Protox.Encode.encode_sint64(unquote(value_var)))
446
  end
447

448
  defp get_encode_value_body(:fixed32, value_var) do
449
    quote(do: Protox.Encode.encode_fixed32(unquote(value_var)))
450
  end
451

452
  defp get_encode_value_body(:fixed64, value_var) do
453
    quote(do: Protox.Encode.encode_fixed64(unquote(value_var)))
454
  end
455

456
  defp get_encode_value_body(:sfixed32, value_var) do
457
    quote(do: Protox.Encode.encode_sfixed32(unquote(value_var)))
458
  end
459

460
  defp get_encode_value_body(:sfixed64, value_var) do
461
    quote(do: Protox.Encode.encode_sfixed64(unquote(value_var)))
462
  end
463

464
  defp get_encode_value_body(:float, value_var) do
465
    quote(do: Protox.Encode.encode_float(unquote(value_var)))
466
  end
467

468
  defp get_encode_value_body(:double, value_var) do
469
    quote(do: Protox.Encode.encode_double(unquote(value_var)))
470
  end
471

472
  defp make_encode_field_fun_name(field) when is_atom(field) do
473
    String.to_atom("encode_#{field}")
1,960✔
474
  end
475

476
  defp get_required_fields(fields) do
477
    for %Field{label: :required, name: name} <- fields, do: name
90✔
478
  end
479
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