Packages
protox
1.7.0
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
def define(fields, required_fields, syntax, opts \\ []) do
{keep_unknown_fields, _opts} = Keyword.pop(opts, :keep_unknown_fields, true)
{oneofs, fields_without_oneofs} = Protox.Defs.split_oneofs(fields)
encode_fun = make_encode_fun(oneofs, fields_without_oneofs, keep_unknown_fields)
encode_oneof_funs = make_encode_oneof_funs(oneofs)
encode_field_funs = make_encode_field_funs(fields, required_fields, syntax)
encode_unknown_fields_fun = make_encode_unknown_fields_fun(keep_unknown_fields)
quote do
unquote(encode_fun)
unquote(encode_oneof_funs)
unquote(encode_field_funs)
unquote(encode_unknown_fields_fun)
end
end
defp make_encode_fun(oneofs, fields, keep_unknown_fields) do
quote(do: [])
|> make_encode_oneof_fun(oneofs)
|> make_encode_fun_field(fields, keep_unknown_fields)
|> make_encode_fun_body()
end
defp make_encode_fun_body([] = _ast) do
quote do
@spec encode(struct) :: {:ok, iodata}
def encode(msg) do
{:ok, encode!(msg)}
end
@spec encode!(struct) :: iodata
def encode!(_msg), do: []
end
end
defp make_encode_fun_body(ast) do
quote do
@spec encode(struct) :: {:ok, iodata} | {:error, any}
def encode(msg) do
try do
{:ok, encode!(msg)}
rescue
e in [Protox.EncodingError, Protox.RequiredFieldsError] ->
{:error, e}
end
end
@spec encode!(struct) :: iodata | no_return
def encode!(msg), do: unquote(ast)
end
end
defp make_encode_fun_field(ast, fields, keep_unknown_fields) do
ast =
Enum.reduce(fields, ast, fn %Protox.Field{} = field, ast_acc ->
fun_name = String.to_atom("encode_#{field.name}")
quote(do: unquote(ast_acc) |> unquote(fun_name)(msg))
end)
case keep_unknown_fields do
true -> quote(do: unquote(ast) |> encode_unknown_fields(msg))
false -> ast
end
end
defp make_encode_oneof_fun(ast, oneofs) do
Enum.reduce(oneofs, ast, fn {parent_name, _children}, ast_acc ->
fun_name = String.to_atom("encode_#{parent_name}")
quote(do: unquote(ast_acc) |> unquote(fun_name)(msg))
end)
end
defp parent_name(_, [%Protox.Field{label: :proto3_optional, kind: {_, name}}]), do: name
defp parent_name(name, _), do: name
defp parent_data_key(_, [%Protox.Field{label: :proto3_optional, name: child_name}]) do
child_name
end
defp parent_data_key(parent_name, _), do: parent_name
defp make_encode_oneof_funs(oneofs) do
for {parent_name, children} <- oneofs do
nil_case =
quote do
nil -> acc
end
children_case_ast =
nil_case ++
(children
|> Enum.map(fn %Field{} = child_field ->
encode_child_fun_name = String.to_atom("encode_#{child_field.name}")
quote do
{unquote(child_field.name), _field_value} ->
unquote(encode_child_fun_name)(acc, msg)
end
end)
|> List.flatten())
parent_name = parent_name(parent_name, children)
encode_parent_fun_name = String.to_atom("encode_#{parent_name}")
parent_data_key = parent_data_key(parent_name, children)
quote do
defp unquote(encode_parent_fun_name)(acc, msg) do
case msg.unquote(parent_data_key) do
unquote(children_case_ast)
end
end
end
end
end
defp make_encode_field_funs(fields, required_fields, syntax) do
vars = %{
acc: Macro.var(:acc, __MODULE__),
msg: Macro.var(:msg, __MODULE__)
}
for %Field{name: name} = field <- fields do
required = name in required_fields
fun_name = String.to_atom("encode_#{name}")
fun_ast = make_encode_field_body(field, required, syntax, vars)
quote do
defp unquote(fun_name)(unquote(vars.acc), unquote(vars.msg)) do
try do
unquote(fun_ast)
rescue
ArgumentError ->
reraise Protox.EncodingError.new(unquote(name), "invalid field value"),
__STACKTRACE__
end
end
end
end
end
defp make_encode_field_body(%Field{kind: {:scalar, default}} = field, required, syntax, vars) do
key = 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)
case syntax do
:proto2 ->
if required do
quote do
case unquote(vars.msg).unquote(field.name) do
nil -> raise Protox.RequiredFieldsError.new([unquote(field.name)])
_ -> [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
end
end
else
quote do
case unquote(var) do
nil -> unquote(vars.acc)
_ -> [unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
end
end
end
:proto3 ->
quote do
# Use == rather than pattern match for float comparison
if unquote(var) == unquote(default) do
unquote(vars.acc)
else
[unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
end
end
end
end
# Generate the AST to encode child `field.name` of oneof `parent_field`
defp make_encode_field_body(
%Field{kind: {:oneof, parent_field}} = child_field,
_required,
_syntax,
vars
) do
key = make_key_bytes(child_field.tag, child_field.type)
var = Macro.var(:child_field_value, __MODULE__)
encode_value_ast = get_encode_value_body(child_field.type, var)
case child_field.label do
:proto3_optional ->
quote do
case unquote(vars.msg).unquote(child_field.name) do
{_, unquote(var)} ->
[unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
unquote(var) when not is_nil(unquote(var)) ->
[unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
_ ->
[unquote(vars.acc)]
end
end
_ ->
# 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
{_, unquote(var)} = unquote(vars.msg).unquote(parent_field)
[unquote(vars.acc), unquote(key), unquote(encode_value_ast)]
end
end
end
defp make_encode_field_body(%Field{kind: :packed} = field, _required, _syntax, vars) do
key = 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)
values -> [unquote(vars.acc), unquote(key), unquote(encode_packed_ast)]
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)
values -> [unquote(vars.acc), unquote(encode_repeated_ast)]
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
key = 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)
map_key_key_bytes = make_key_bytes(1, map_key_type)
map_value_key_bytes = make_key_bytes(2, map_value_type)
map_keys_len = byte_size(map_value_key_bytes) + byte_size(map_key_key_bytes)
quote do
map = Map.fetch!(unquote(vars.msg), unquote(field.name))
Enum.reduce(map, unquote(vars.acc), fn {unquote(k_var), unquote(v_var)},
unquote(vars.acc) ->
map_key_value_bytes = :binary.list_to_bin([unquote(encode_map_key_ast)])
map_key_value_len = byte_size(map_key_value_bytes)
map_value_value_bytes = :binary.list_to_bin([unquote(encode_map_value_ast)])
map_value_value_len = byte_size(map_value_value_bytes)
len =
Protox.Varint.encode(unquote(map_keys_len) + map_key_value_len + map_value_value_len)
[
unquote(vars.acc),
unquote(key),
len,
unquote(map_key_key_bytes),
map_key_value_bytes,
unquote(map_value_key_bytes),
map_value_value_bytes
]
end)
end
end
defp make_encode_unknown_fields_fun(true = _keep_unknown_fields) do
quote do
defp encode_unknown_fields(acc, msg) do
Enum.reduce(msg.__struct__.unknown_fields(msg), acc, fn {tag, wire_type, bytes}, acc ->
case wire_type do
0 ->
[acc, Protox.Encode.make_key_bytes(tag, :int32), bytes]
1 ->
[acc, Protox.Encode.make_key_bytes(tag, :double), bytes]
2 ->
len_bytes = bytes |> byte_size() |> Protox.Varint.encode()
[acc, Protox.Encode.make_key_bytes(tag, :packed), len_bytes, bytes]
5 ->
[acc, Protox.Encode.make_key_bytes(tag, :float), bytes]
end
end)
end
end
end
defp make_encode_unknown_fields_fun(false = _keep_unknown_fields) do
[]
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
{bytes, len} =
Enum.reduce(values, {[], 0}, fn unquote(value_var), {acc, len} ->
value_bytes = :binary.list_to_bin([unquote(encode_value_ast)])
{[acc, value_bytes], len + byte_size(value_bytes)}
end)
[Protox.Varint.encode(len), bytes]
end
end
defp make_encode_repeated_body(tag, type) do
key = make_key_bytes(tag, type)
value_var = Macro.var(:value, __MODULE__)
encode_value_ast = get_encode_value_body(type, value_var)
quote do
Enum.reduce(values, [], fn unquote(value_var), acc ->
[acc, unquote(key), unquote(encode_value_ast)]
end)
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
# Flatten an iolist into a binary at generation-time.
defp make_key_bytes(tag, ty) do
IO.iodata_to_binary(Protox.Encode.make_key_bytes(tag, ty))
end
end