Packages
protox
2.0.8
2.0.9
2.0.8
2.0.7
2.0.6
2.0.5
2.0.4
2.0.3
2.0.2
2.0.1
2.0.0
2.0.0-dev
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
retired
1.7.3
1.7.2
1.7.1
1.7.0
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.6.2
1.6.1
1.6.0
1.5.1
1.5.0
1.4.0
1.3.2
1.3.1
1.3.0
1.2.4
1.2.3
1.2.2
1.2.1
1.2.0
1.1.1
1.1.0
1.0.0
0.25.0
0.24.0
0.23.1
0.23.0
0.22.0
0.21.0
0.20.0
0.19.1
0.19.0
0.18.0
0.17.0
0.16.2
0.16.1
0.16.0
0.15.2
0.15.1
0.15.0
0.14.0
0.13.0
0.12.1
0.12.0
0.11.1
0.11.0
0.10.0
0.9.1
0.9.0
0.8.0
0.7.1
0.7.0
A fast, easy to use and 100% conformant Elixir library for Google Protocol Buffers (aka protobuf)
Current section
Files
Jump to
Current section
Files
lib/protox/define_encoder.ex
defmodule Protox.DefineEncoder do
@moduledoc false
# Internal. Generates the encoder of a message.
alias Protox.{Field, OneOf, Scalar}
def define(fields, syntax, opts \\ []) do
vars = %{
acc: Macro.var(:acc, __MODULE__),
acc_size: Macro.var(:acc_size, __MODULE__),
child_field_value: Macro.var(:child_field_value, __MODULE__),
msg: Macro.var(:msg, __MODULE__)
}
required_fields = get_required_fields(fields)
%{oneofs: oneofs, proto3_optionals: proto3_optionals, others: fields_without_oneofs} =
Protox.Defs.split_oneofs(fields)
top_level_encode_fun =
make_top_level_encode_fun(oneofs, proto3_optionals ++ fields_without_oneofs)
encode_oneof_funs = make_encode_oneof_funs(oneofs, syntax, vars)
encode_field_funs = make_encode_field_funs(fields, required_fields, syntax, vars)
encode_unknown_fields_fun = make_encode_unknown_fields_fun(vars, opts)
quote do
unquote(top_level_encode_fun)
unquote_splicing(encode_oneof_funs)
unquote_splicing(encode_field_funs)
unquote(encode_unknown_fields_fun)
end
end
defp make_top_level_encode_fun(oneofs, fields) do
quote(do: {_acc = [], _acc_size = 0})
|> make_encode_oneof_fun(oneofs)
|> make_encode_fun_field(fields)
|> make_encode_fun_body()
end
defp make_encode_fun_body(ast) do
quote do
@spec encode(t()) :: {:ok, iodata(), non_neg_integer()} | {:error, any()}
def encode(msg) do
msg |> encode!() |> Tuple.insert_at(0, :ok)
rescue
e in [Protox.EncodingError, Protox.RequiredFieldsError] ->
{:error, e}
end
@spec encode!(t()) :: {iodata(), non_neg_integer()} | no_return()
def encode!(msg), do: unquote(ast)
end
end
defp make_encode_fun_field(ast, fields) do
ast =
Enum.reduce(fields, ast, fn %Protox.Field{} = field, ast_acc ->
quote do
unquote(ast_acc)
|> unquote(make_encode_field_fun_name(field.name))(msg)
end
end)
quote do
unquote(ast) |> encode_unknown_fields(msg)
end
end
defp make_encode_oneof_fun(ast, oneofs) do
Enum.reduce(oneofs, ast, fn {parent_name, _children}, ast_acc ->
quote do
unquote(ast_acc)
|> unquote(make_encode_field_fun_name(parent_name))(msg)
end
end)
end
defp make_encode_oneof_funs(oneofs, syntax, vars) do
for {parent_name, children} <- oneofs do
nil_clause =
quote do
nil -> {unquote(vars.acc), unquote(vars.acc_size)}
end
children_clauses_ast =
Enum.flat_map(children, fn %Field{} = child_field ->
encode_child_body = make_encode_field_body(child_field, false, syntax, vars)
quote do
{unquote(child_field.name), unquote(vars.child_field_value)} ->
unquote(encode_child_body)
end
end)
quote do
defp unquote(make_encode_field_fun_name(parent_name))(
{unquote(vars.acc), unquote(vars.acc_size)},
msg
) do
case msg.unquote(parent_name) do
unquote(nil_clause ++ children_clauses_ast)
end
end
end
end
end
defp make_encode_field_funs(fields, required_fields, syntax, vars) do
fields =
Enum.reject(fields, fn
%Field{label: :proto3_optional, kind: %OneOf{}} -> false
%Field{kind: %OneOf{}} -> true
_ -> false
end)
for %Field{name: name} = field <- fields do
required = name in required_fields
fun_name = make_encode_field_fun_name(name)
fun_ast = make_encode_field_body(field, required, syntax, vars)
quote do
defp unquote(fun_name)({unquote(vars.acc), unquote(vars.acc_size)}, unquote(vars.msg)) do
unquote(fun_ast)
rescue
ArgumentError ->
reraise Protox.EncodingError.new(unquote(name), "invalid field value"),
__STACKTRACE__
end
end
end
end
defp make_encode_field_body(%Field{kind: %Scalar{}} = field, required, syntax, vars) do
{key, key_size} = Protox.Encode.make_key_bytes(field.tag, field.type)
var = quote do: unquote(vars.msg).unquote(field.name)
encode_value_ast = get_encode_value_body(field.type, var)
encode_value_clause =
quote do
{value_bytes, value_bytes_size} = unquote(encode_value_ast)
{
[unquote(key), value_bytes | unquote(vars.acc)],
unquote(vars.acc_size) + unquote(key_size) + value_bytes_size
}
end
case {syntax, required} do
{:proto2, true = _required} ->
quote do
case unquote(vars.msg).unquote(field.name) do
nil -> raise Protox.RequiredFieldsError.new([unquote(field.name)])
_ -> unquote(encode_value_clause)
end
end
{:proto2, false = _required} ->
quote do
case unquote(var) do
nil -> {unquote(vars.acc), unquote(vars.acc_size)}
_ -> unquote(encode_value_clause)
end
end
{:proto3, _required} ->
quote do
# Use == rather than pattern match for float comparison
if unquote(var) == unquote(field.kind.default_value) do
{unquote(vars.acc), unquote(vars.acc_size)}
else
unquote(encode_value_clause)
end
end
end
end
defp make_encode_field_body(%Field{label: :proto3_optional, kind: %OneOf{}} = field, _required, _syntax, vars) do
{key, key_size} = Protox.Encode.make_key_bytes(field.tag, field.type)
var = Macro.var(:child_field_value, __MODULE__)
encode_value_ast = get_encode_value_body(field.type, var)
quote do
case unquote(vars.msg).unquote(field.name) do
nil ->
{unquote(vars.acc), unquote(vars.acc_size)}
unquote(var) ->
{value_bytes, value_bytes_size} = unquote(encode_value_ast)
{
[unquote(key), value_bytes | unquote(vars.acc)],
unquote(vars.acc_size) + unquote(key_size) + value_bytes_size
}
end
end
end
defp make_encode_field_body(%Field{kind: %OneOf{}} = field, _required, _syntax, vars) do
{key, key_size} = Protox.Encode.make_key_bytes(field.tag, field.type)
encode_value_ast = get_encode_value_body(field.type, vars.child_field_value)
# The dispatch on the correct child is performed by the parent encoding function,
# this is why we don't check if the child is set.
quote do
{value_bytes, value_bytes_size} = unquote(encode_value_ast)
{
[unquote(key), value_bytes | unquote(vars.acc)],
unquote(vars.acc_size) + unquote(key_size) + value_bytes_size
}
end
end
defp make_encode_field_body(%Field{kind: :packed} = field, _required, _syntax, vars) do
{key_bytes, key_size} = Protox.Encode.make_key_bytes(field.tag, :packed)
encode_packed_ast = make_encode_packed_body(field.type)
quote do
case unquote(vars.msg).unquote(field.name) do
[] ->
{unquote(vars.acc), unquote(vars.acc_size)}
values ->
{packed_bytes, packed_size} = unquote(encode_packed_ast)
{
[unquote(key_bytes), packed_bytes | unquote(vars.acc)],
unquote(vars.acc_size) + unquote(key_size) + packed_size
}
end
end
end
defp make_encode_field_body(%Field{kind: :unpacked} = field, _required, _syntax, vars) do
encode_repeated_ast = make_encode_repeated_body(field.tag, field.type)
quote do
case unquote(vars.msg).unquote(field.name) do
[] ->
{unquote(vars.acc), unquote(vars.acc_size)}
values ->
{value_bytes, value_size} = unquote(encode_repeated_ast)
{[value_bytes | unquote(vars.acc)], unquote(vars.acc_size) + value_size}
end
end
end
defp make_encode_field_body(%Field{kind: :map} = field, _required, _syntax, vars) do
# Each key/value entry of a map has the same layout as a message.
# https://developers.google.com/protocol-buffers/docs/proto3#backwards-compatibility
{field_key, field_key_size} = Protox.Encode.make_key_bytes(field.tag, :map_entry)
{map_key_type, map_value_type} = field.type
k_var = Macro.var(:k, __MODULE__)
v_var = Macro.var(:v, __MODULE__)
encode_map_key_ast = get_encode_value_body(map_key_type, k_var)
encode_map_value_ast = get_encode_value_body(map_value_type, v_var)
{k_key_bytes, k_key_size} = Protox.Encode.make_key_bytes(1, map_key_type)
{v_key_bytes, v_key_size} = Protox.Encode.make_key_bytes(2, map_value_type)
keys_len = k_key_size + v_key_size
quote do
map = Map.fetch!(unquote(vars.msg), unquote(field.name))
if map_size(map) == 0 do
{unquote(vars.acc), unquote(vars.acc_size)}
else
Enum.reduce(
map,
{unquote(vars.acc), unquote(vars.acc_size)},
fn {unquote(k_var), unquote(v_var)}, {unquote(vars.acc), unquote(vars.acc_size)} ->
{k_value_bytes, k_value_len} = unquote(encode_map_key_ast)
{v_value_bytes, v_value_len} = unquote(encode_map_value_ast)
len = unquote(keys_len) + k_value_len + v_value_len
{len_varint, len_varint_size} = Protox.Varint.encode(len)
unquote(vars.acc) = [
<<unquote(field_key), len_varint::binary, unquote(k_key_bytes)>>,
k_value_bytes,
unquote(v_key_bytes),
v_value_bytes
| unquote(vars.acc)
]
{
unquote(vars.acc),
unquote(vars.acc_size) + unquote(field_key_size + keys_len) + k_value_len +
v_value_len + len_varint_size
}
end
)
end
end
end
defp make_encode_unknown_fields_fun(vars, opts) do
unknown_fields_name = Keyword.fetch!(opts, :unknown_fields_name)
quote do
defp encode_unknown_fields({unquote(vars.acc), unquote(vars.acc_size)}, msg) do
Enum.reduce(
msg.unquote(unknown_fields_name),
{unquote(vars.acc), unquote(vars.acc_size)},
fn {tag, wire_type, bytes}, {unquote(vars.acc), unquote(vars.acc_size)} ->
case wire_type do
0 ->
{key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :int32)
{
[unquote(vars.acc), <<key_bytes::binary, bytes::binary>>],
unquote(vars.acc_size) + key_size + byte_size(bytes)
}
1 ->
{key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :double)
{
[unquote(vars.acc), <<key_bytes::binary, bytes::binary>>],
unquote(vars.acc_size) + key_size + byte_size(bytes)
}
2 ->
{len_bytes, len_size} = bytes |> byte_size() |> Protox.Varint.encode()
{key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :packed)
{
[unquote(vars.acc), <<key_bytes::binary, len_bytes::binary, bytes::binary>>],
unquote(vars.acc_size) + key_size + len_size + byte_size(bytes)
}
5 ->
{key_bytes, key_size} = Protox.Encode.make_key_bytes(tag, :float)
{
[unquote(vars.acc), <<key_bytes::binary, bytes::binary>>],
unquote(vars.acc_size) + key_size + byte_size(bytes)
}
end
end
)
end
end
end
defp make_encode_packed_body(type) do
value_var = Macro.var(:value, __MODULE__)
encode_value_ast = get_encode_value_body(type, value_var)
quote do
{value_bytes, value_size} =
Enum.reduce(
values,
{_local_acc = [], _local_acc_size = 0},
fn unquote(value_var), {local_acc, local_acc_size} ->
{value_bytes, value_bytes_size} = unquote(encode_value_ast)
{
[value_bytes | local_acc],
local_acc_size + value_bytes_size
}
end
)
{value_size_bytes, value_size_size} = Protox.Varint.encode(value_size)
{[value_size_bytes, Enum.reverse(value_bytes)], value_size + value_size_size}
end
end
defp make_encode_repeated_body(tag, type) do
{key_bytes, key_bytes_sz} = Protox.Encode.make_key_bytes(tag, type)
value_var = Macro.var(:value, __MODULE__)
encode_value_ast = get_encode_value_body(type, value_var)
quote do
{value_bytes, value_size} =
Enum.reduce(
values,
{_local_acc = [], _local_acc_size = 0},
fn unquote(value_var), {local_acc, local_acc_size} ->
{value_bytes, value_bytes_size} = unquote(encode_value_ast)
{
[value_bytes, unquote(key_bytes) | local_acc],
local_acc_size + unquote(key_bytes_sz) + value_bytes_size
}
end
)
{Enum.reverse(value_bytes), value_size}
end
end
defp get_encode_value_body({:message, _}, value_var) do
quote do
Protox.Encode.encode_message(unquote(value_var))
end
end
defp get_encode_value_body({:enum, enum}, value_var) do
quote do
unquote(value_var) |> unquote(enum).encode() |> Protox.Encode.encode_enum()
end
end
defp get_encode_value_body(:bool, value_var) do
quote(do: Protox.Encode.encode_bool(unquote(value_var)))
end
defp get_encode_value_body(:bytes, value_var) do
quote(do: Protox.Encode.encode_bytes(unquote(value_var)))
end
defp get_encode_value_body(:string, value_var) do
quote(do: Protox.Encode.encode_string(unquote(value_var)))
end
defp get_encode_value_body(:int32, value_var) do
quote(do: Protox.Encode.encode_int32(unquote(value_var)))
end
defp get_encode_value_body(:int64, value_var) do
quote(do: Protox.Encode.encode_int64(unquote(value_var)))
end
defp get_encode_value_body(:uint32, value_var) do
quote(do: Protox.Encode.encode_uint32(unquote(value_var)))
end
defp get_encode_value_body(:uint64, value_var) do
quote(do: Protox.Encode.encode_uint64(unquote(value_var)))
end
defp get_encode_value_body(:sint32, value_var) do
quote(do: Protox.Encode.encode_sint32(unquote(value_var)))
end
defp get_encode_value_body(:sint64, value_var) do
quote(do: Protox.Encode.encode_sint64(unquote(value_var)))
end
defp get_encode_value_body(:fixed32, value_var) do
quote(do: Protox.Encode.encode_fixed32(unquote(value_var)))
end
defp get_encode_value_body(:fixed64, value_var) do
quote(do: Protox.Encode.encode_fixed64(unquote(value_var)))
end
defp get_encode_value_body(:sfixed32, value_var) do
quote(do: Protox.Encode.encode_sfixed32(unquote(value_var)))
end
defp get_encode_value_body(:sfixed64, value_var) do
quote(do: Protox.Encode.encode_sfixed64(unquote(value_var)))
end
defp get_encode_value_body(:float, value_var) do
quote(do: Protox.Encode.encode_float(unquote(value_var)))
end
defp get_encode_value_body(:double, value_var) do
quote(do: Protox.Encode.encode_double(unquote(value_var)))
end
defp make_encode_field_fun_name(field) when is_atom(field) do
String.to_atom("encode_#{field}")
end
defp get_required_fields(fields) do
for %Field{label: :required, name: name} <- fields, do: name
end
end