Packages

A pure Elixir implementation of Google Protobuf. A fork of tony612's package.

Current section

Files

Jump to
protocol_buffers lib protobuf protoc generator message.ex
Raw

lib/protobuf/protoc/generator/message.ex

defmodule Protobuf.Protoc.Generator.Message do
alias Protobuf.Protoc.Generator.Util
alias Protobuf.TypeUtil
alias Protobuf.Protoc.Generator.Enum, as: EnumGenerator
def generate_list(ctx, descs) do
Enum.map(descs, fn desc -> generate(ctx, desc) end)
end
def generate(ctx, desc) do
msg_struct = parse_desc(ctx, desc)
ctx = %{ctx | namespace: msg_struct[:new_namespace]}
[gen_msg(ctx.syntax, msg_struct)] ++ gen_nested_msgs(ctx, desc) ++ gen_nested_enums(ctx, desc)
end
def parse_desc(%{namespace: ns} = ctx, desc) do
new_ns = ns ++ [Util.trans_name(desc.name)]
fields = get_fields(ctx, desc)
%{
new_namespace: new_ns,
name: Util.mod_name(ctx, new_ns),
options: msg_opts_str(ctx, desc.options),
structs: structs_str(desc),
typespec: typespec_str(fields, desc.oneof_decl),
fields: fields,
oneofs: oneofs_str(desc.oneof_decl)
}
end
defp gen_msg(syntax, msg_struct) do
Protobuf.Protoc.Template.message(
msg_struct[:name],
msg_struct[:options],
msg_struct[:structs],
msg_struct[:typespec],
msg_struct[:oneofs],
gen_fields(syntax, msg_struct[:fields])
)
end
defp gen_nested_msgs(ctx, desc) do
Enum.map(desc.nested_type, fn msg_desc -> generate(ctx, msg_desc) end)
end
defp gen_nested_enums(ctx, desc) do
Enum.map(desc.enum_type, fn enum_desc -> EnumGenerator.generate(ctx, enum_desc) end)
end
defp gen_fields(syntax, fields) do
Enum.map(fields, fn %{opts: opts} = f ->
opts_str = Util.options_to_str(opts)
opts_str = if opts_str == "", do: "", else: ", " <> opts_str
label_str =
if syntax == :proto3 && f[:label] != "repeated", do: "", else: "#{f[:label]}: true, "
":#{f[:name]}, #{f[:number]}, #{label_str}type: #{f[:type]}#{opts_str}"
end)
end
def msg_opts_str(%{syntax: syntax}, opts) do
msg_options = opts
opts = %{
syntax: syntax,
map: msg_options && msg_options.map_entry,
deprecated: msg_options && msg_options.deprecated
}
str = Util.options_to_str(opts)
if String.length(str) > 0, do: ", " <> str, else: ""
end
def structs_str(struct) do
fields = Enum.filter(struct.field, fn f -> !f.oneof_index end)
Enum.map_join(struct.oneof_decl ++ fields, ", ", fn f -> ":#{f.name}" end)
end
def typespec_str([], []), do: " @type t :: %__MODULE__{}\n"
def typespec_str(fields, oneofs) do
longest_field = fields |> Enum.max_by(&String.length(&1[:name]))
longest_width = String.length(longest_field[:name])
fields = Enum.filter(fields, fn f -> !f[:oneof] end)
types =
Enum.map(oneofs, fn f ->
{fmt_type_name(f.name, longest_width), "{atom, any}"}
end) ++
Enum.map(fields, fn f ->
{fmt_type_name(f[:name], longest_width), fmt_type(f)}
end)
" @type t :: %__MODULE__{\n" <>
Enum.map_join(types, ",\n", fn {k, v} ->
" #{k} #{v}"
end) <> "\n }\n"
end
defp oneofs_str(oneofs) do
oneofs
|> Enum.with_index()
|> Enum.map(fn {oneof, index} ->
"oneof :#{oneof.name}, #{index}"
end)
end
defp fmt_type_name(name, len) do
String.pad_trailing("#{name}:", len + 1)
end
defp fmt_type(%{opts: %{enum: true}, label: "repeated"}), do: "[atom | integer]"
defp fmt_type(%{opts: %{enum: true}}), do: "atom | integer"
defp fmt_type(%{opts: %{map: true}, map: {{k_type, k_name}, {v_type, v_name}}}) do
k_type = type_to_spec(k_type, k_name)
v_type = type_to_spec(v_type, v_name)
"%{#{k_type} => #{v_type}}"
end
defp fmt_type(%{label: "repeated", type_enum: type_enum, type: type}) do
"[#{type_to_spec(type_enum, type, true)}]"
end
defp fmt_type(%{type_enum: type_enum, type: type}) do
"#{type_to_spec(type_enum, type)}"
end
defp type_to_spec(enum, type, repeated \\ false)
defp type_to_spec(:TYPE_MESSAGE, type, true),
do: TypeUtil.enum_to_spec(:TYPE_MESSAGE, type, true)
defp type_to_spec(:TYPE_MESSAGE, type, false),
do: TypeUtil.enum_to_spec(:TYPE_MESSAGE, type, false)
defp type_to_spec(enum, _, _), do: TypeUtil.enum_to_spec(enum)
def get_fields(ctx, desc) do
oneofs = Enum.map(desc.oneof_decl, & &1.name)
nested_maps = nested_maps(ctx, desc)
Enum.map(desc.field, fn f -> get_field(ctx, f, nested_maps, oneofs) end)
end
def get_field(ctx, f, nested_maps, oneofs) do
opts = field_options(f)
map = nested_maps[f.type_name]
opts = if map, do: Map.put(opts, :map, true), else: opts
opts =
if length(oneofs) > 0 && f.oneof_index, do: Map.put(opts, :oneof, f.oneof_index), else: opts
type = field_type_name(ctx, f)
%{
name: f.name,
number: f.number,
label: label_name(f.label),
type: type,
type_enum: f.type,
opts: opts,
map: map,
oneof: f.oneof_index
}
end
defp field_type_name(ctx, f) do
type = TypeUtil.from_enum(f.type)
if f.type_name && (type == :enum || type == :message) do
Util.type_from_type_name(ctx, f.type_name)
else
":#{type}"
end
end
# Map of protobuf are actually nested(one level) messages
defp nested_maps(ctx, desc) do
full_name = Util.join_name([ctx.package | ctx.namespace] ++ [desc.name])
prefix = "." <> full_name
Enum.reduce(desc.nested_type, %{}, fn desc, acc ->
cond do
desc.options && desc.options.map_entry ->
[k, v] = Enum.sort(desc.field, &(&1.number < &2.number))
pair = {{k.type, field_type_name(ctx, k)}, {v.type, field_type_name(ctx, v)}}
Map.put(acc, Util.join_name([prefix, desc.name]), pair)
true ->
acc
end
end)
end
defp field_options(f) do
opts = %{enum: f.type == :TYPE_ENUM, default: default_value(f.type, f.default_value)}
if f.options, do: merge_field_options(opts, f), else: opts
end
defp label_name(:LABEL_OPTIONAL), do: "optional"
defp label_name(:LABEL_REQUIRED), do: "required"
defp label_name(:LABEL_REPEATED), do: "repeated"
defp default_value(_, ""), do: nil
defp default_value(_, nil), do: nil
defp default_value(t, val) do
v = do_default_value(t, val)
if v == nil, do: v, else: inspect(v)
end
defp do_default_value(:TYPE_DOUBLE, v), do: float_default(v)
defp do_default_value(:TYPE_FLOAT, v), do: float_default(v)
defp do_default_value(:TYPE_INT64, v), do: int_default(v)
defp do_default_value(:TYPE_UINT64, v), do: int_default(v)
defp do_default_value(:TYPE_INT32, v), do: int_default(v)
defp do_default_value(:TYPE_FIXED64, v), do: int_default(v)
defp do_default_value(:TYPE_FIXED32, v), do: int_default(v)
defp do_default_value(:TYPE_BOOL, v), do: String.to_atom(v)
defp do_default_value(:TYPE_STRING, v), do: v
defp do_default_value(:TYPE_BYTES, v), do: v
defp do_default_value(:TYPE_UINT32, v), do: int_default(v)
defp do_default_value(:TYPE_ENUM, v), do: String.to_atom(v)
defp do_default_value(:TYPE_SFIXED32, v), do: int_default(v)
defp do_default_value(:TYPE_SFIXED64, v), do: int_default(v)
defp do_default_value(:TYPE_SINT32, v), do: int_default(v)
defp do_default_value(:TYPE_SINT64, v), do: int_default(v)
defp do_default_value(_, _), do: nil
defp float_default(value) do
case Float.parse(value) do
{v, _} -> v
:error -> value
end
end
defp int_default(value) do
case Integer.parse(value) do
{v, _} -> v
:error -> value
end
end
defp merge_field_options(opts, f) do
opts
|> Map.put(:packed, f.options.packed)
|> Map.put(:deprecated, f.options.deprecated)
end
end