Skip to content

Commit

Permalink
Don't create and encode unknown fields when keep_unknown_fields == false
Browse files Browse the repository at this point in the history
  • Loading branch information
ahamez committed Oct 31, 2020
1 parent 4e7e53f commit a9109e4
Show file tree
Hide file tree
Showing 3 changed files with 57 additions and 23 deletions.
30 changes: 21 additions & 9 deletions lib/protox/define_encoder.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ defmodule Protox.DefineEncoder do
@moduledoc false
# Internal. Generates the encoder of a message.

def define(fields, required_fields, syntax) do
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)
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()
encode_unknown_fields_fun = make_encode_unknown_fields_fun(keep_unknown_fields)

quote do
@spec encode(struct) :: {:ok, iodata} | {:error, any}
Expand All @@ -29,25 +31,30 @@ defmodule Protox.DefineEncoder do
end
end

defp make_encode_fun(oneofs, fields) do
defp make_encode_fun(oneofs, fields, keep_unknown_fields) do
ast = quote do: []
ast = make_encode_oneof_fun(ast, oneofs)
make_encode_fun_field(ast, fields)
make_encode_fun_field(ast, fields, keep_unknown_fields)
end

defp make_encode_fun_field(ast, []) do
defp make_encode_fun_field(ast, [], _keep_unknown_fields = true) do
# credo:disable-for-next-line Credo.Check.Readability.SinglePipe
quote do: unquote(ast) |> encode_unknown_fields(msg)
end

defp make_encode_fun_field(ast, [field | fields]) do
defp make_encode_fun_field(ast, [], _keep_unknown_fields = false) do
# credo:disable-for-next-line Credo.Check.Readability.SinglePipe
quote do: unquote(ast)
end

defp make_encode_fun_field(ast, [field | fields], keep_unknown_fields) do
{_, _, name, _, _} = field
fun_name = String.to_atom("encode_#{name}")

# credo:disable-for-next-line Credo.Check.Readability.SinglePipe
ast = quote do: unquote(ast) |> unquote(fun_name)(msg)

make_encode_fun_field(ast, fields)
make_encode_fun_field(ast, fields, keep_unknown_fields)
end

defp make_encode_oneof_fun(ast, []), do: ast
Expand Down Expand Up @@ -224,7 +231,7 @@ defmodule Protox.DefineEncoder do
end
end

defp make_encode_unknown_fields_fun() do
defp make_encode_unknown_fields_fun(_keep_unknown_fields = true) do
quote do
defp encode_unknown_fields(acc, msg) do
Enum.reduce(msg.__struct__.unknown_fields(msg), acc, fn {tag, wire_type, bytes}, acc ->
Expand All @@ -247,6 +254,11 @@ defmodule Protox.DefineEncoder do
end
end

defp make_encode_unknown_fields_fun(_keep_unknown_fields = false) do
quote do
end
end

defp make_encode_packed_body(type) do
var = quote do: value
encode_value_ast = get_encode_value_body(type, var)
Expand Down
46 changes: 33 additions & 13 deletions lib/protox/define_message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@ defmodule Protox.DefineMessage do
@moduledoc false

def define(messages, opts \\ []) do
{keep_unknown_fields, _opts} = Keyword.pop(opts, :keep_unknown_fields, true)

for {msg_name, syntax, fields} <- messages do
fields = Enum.sort(fields, &(elem(&1, 0) < elem(&2, 0)))
unknown_fields = make_unknown_fields(:__uf__, fields)
struct_fields = make_struct_fields(fields, unknown_fields, syntax)
unknown_fields_funs = make_unknown_fields_funs(keep_unknown_fields, unknown_fields)
struct_fields = make_struct_fields(fields, syntax, unknown_fields, keep_unknown_fields)
required_fields = make_required_fields(fields)
required_fields_typesecs = make_required_fields_typespec(required_fields)
fields_map = make_fields_map(fields)
fields_by_name_map = make_fields_by_name_map(fields)
encoder = Protox.DefineEncoder.define(fields, required_fields, syntax)
encoder = Protox.DefineEncoder.define(fields, required_fields, syntax, opts)
decoder = Protox.DefineDecoder.define(msg_name, fields, required_fields, opts)
default_fun = make_default_fun(fields)

Expand All @@ -31,18 +34,11 @@ defmodule Protox.DefineMessage do
}
def defs_by_name(), do: unquote(fields_by_name_map)

unquote(unknown_fields_funs)

@spec required_fields() :: unquote(required_fields_typesecs)
def required_fields(), do: unquote(required_fields)

@spec unknown_fields(struct) :: [{non_neg_integer, Protox.Types.tag(), binary}]
def unknown_fields(msg), do: msg.unquote(unknown_fields)

@spec unknown_fields_name() :: unquote(unknown_fields)
def unknown_fields_name(), do: unquote(unknown_fields)

@spec clear_unknown_fields(struct) :: struct
def clear_unknown_fields(msg), do: struct!(msg, [{unknown_fields_name(), []}])

@spec syntax() :: atom
def syntax(), do: unquote(syntax)

Expand All @@ -61,6 +57,24 @@ defmodule Protox.DefineMessage do
end
end

defp make_unknown_fields_funs(_keep_unknown_fields = true, unknown_fields) do
quote do
@spec unknown_fields(struct) :: [{non_neg_integer, Protox.Types.tag(), binary}]
def unknown_fields(msg), do: msg.unquote(unknown_fields)

@spec unknown_fields_name() :: unquote(unknown_fields)
def unknown_fields_name(), do: unquote(unknown_fields)

@spec clear_unknown_fields(struct) :: struct
def clear_unknown_fields(msg), do: struct!(msg, [{unknown_fields_name(), []}])
end
end

defp make_unknown_fields_funs(_keep_unknown_fields = false, _unknown_fields) do
quote do
end
end

defp make_default_fun(fields) do
spec =
quote do
Expand Down Expand Up @@ -106,7 +120,7 @@ defmodule Protox.DefineMessage do
end

# Generate fields of the struct which is created for a message.
defp make_struct_fields(fields, unknown_fields, syntax) do
defp make_struct_fields(fields, syntax, unknown_fields, keep_unknown_fields) do
struct_fields =
for {_, _, name, kind, _} <- fields do
case kind do
Expand All @@ -117,7 +131,13 @@ defmodule Protox.DefineMessage do
{:default, _} when syntax == :proto2 -> {name, nil}
{:default, default_value} when syntax == :proto3 -> {name, default_value}
end
end ++ [{unknown_fields, []}]
end

struct_fields =
case keep_unknown_fields do
true -> struct_fields ++ [{unknown_fields, []}]
false -> struct_fields
end

Enum.uniq(struct_fields)
end
Expand Down
4 changes: 3 additions & 1 deletion test/protox_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,9 @@ defmodule ProtoxTest do

test "Don't keep unknown fields when asked not to" do
bytes = <<8, 42, 25, 246, 40, 92, 143, 194, 53, 69, 64, 136, 241, 4, 83>>
assert NoUf.Sub.decode!(bytes) == %NoUf.Sub{a: 42, b: "", z: -42}
msg = NoUf.Sub.decode!(bytes)
assert msg == %NoUf.Sub{a: 42, b: "", z: -42}
assert Map.get(msg, :__uf__) == nil
end

test "Can export to protoc and read its output (Sub)" do
Expand Down

0 comments on commit a9109e4

Please sign in to comment.