Skip to content

feat: add full_name callback to proto messages #409

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions lib/protobuf.ex
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ defmodule Protobuf do
Protobuf.Builder.new!(__MODULE__, attrs)
end

@impl unquote(__MODULE__)
def full_name(), do: @options[:full_name]

@impl unquote(__MODULE__)
def transform_module() do
nil
Expand Down Expand Up @@ -175,6 +178,12 @@ defmodule Protobuf do
"""
@callback transform_module() :: module() | nil

@doc """
Returns the fully qualified name of the descriptor's target.
"""
@doc since: "0.15.0"
@callback full_name() :: String.t()

@doc """
Decodes the given binary data interpreting it as the Protobuf message `module`.

Expand Down
8 changes: 7 additions & 1 deletion lib/protobuf/protoc/generator/enum.ex
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,17 @@ defmodule Protobuf.Protoc.Generator.Enum do
def generate(%Context{namespace: ns} = ctx, %Google.Protobuf.EnumDescriptorProto{} = desc) do
msg_name = Util.mod_name(ctx, ns ++ [Macro.camelize(desc.name)])

full_name =
([ctx.package] ++ ctx.namespace ++ [desc.name])
|> Enum.reject(&is_nil/1)
|> Enum.join(".")

use_options =
Util.options_to_str(%{
syntax: ctx.syntax,
enum: true,
protoc_gen_elixir_version: "\"#{Util.version()}\""
protoc_gen_elixir_version: "\"#{Util.version()}\"",
full_name: "\"#{full_name}\""
})

descriptor_fun_body =
Expand Down
12 changes: 9 additions & 3 deletions lib/protobuf/protoc/generator/message.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ defmodule Protobuf.Protoc.Generator.Message do
fields = get_fields(ctx, desc)
extensions = get_extensions(desc)

full_name =
([ctx.package] ++ ctx.namespace ++ [desc.name])
|> Enum.reject(&is_nil/1)
|> Enum.join(".")

descriptor_fun_body =
if ctx.gen_descriptors? do
Util.descriptor_fun_body(desc)
Expand All @@ -52,7 +57,7 @@ defmodule Protobuf.Protoc.Generator.Message do
message_template(
comment: Comment.get(ctx),
module: msg_name,
use_options: msg_opts_str(ctx, desc.options),
use_options: msg_opts_str(ctx, desc.options, full_name),
oneofs: desc.oneof_decl,
fields: gen_fields(ctx.syntax, fields),
descriptor_fun_body: descriptor_fun_body,
Expand Down Expand Up @@ -98,14 +103,15 @@ defmodule Protobuf.Protoc.Generator.Message do
":#{f[:name]}, #{f[:number]}, #{label_str}type: #{f[:type]}#{opts_str}"
end

defp msg_opts_str(%{syntax: syntax}, opts) do
defp msg_opts_str(%{syntax: syntax}, opts, full_name) do
msg_options = opts

opts = %{
syntax: syntax,
map: msg_options && msg_options.map_entry,
deprecated: msg_options && msg_options.deprecated,
protoc_gen_elixir_version: "\"#{Util.version()}\""
protoc_gen_elixir_version: "\"#{Util.version()}\"",
full_name: "\"#{full_name}\""
}

str = Util.options_to_str(opts)
Expand Down
6 changes: 6 additions & 0 deletions mix.exs
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ defmodule Protobuf.Mixfile do
"./generated",
["test/protobuf/protoc/proto/no_package.proto"]
)

protoc!(
"-I test/protobuf/protoc/proto --elixir_opt=include_docs=true",
"./generated",
["test/protobuf/protoc/proto/full_name.proto"]
)
end

defp gen_bench_protos(_args) do
Expand Down
4 changes: 2 additions & 2 deletions test/protobuf/protobuf_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ defmodule Protobuf.ProtobufTest do
use ExUnit.Case, async: false

test "load_extensions/0 is a noop" do
assert loaded_extensions() == 18
assert loaded_extensions() == 19
Protobuf.load_extensions()
assert loaded_extensions() == 18
assert loaded_extensions() == 19
end

describe "encode/1" do
Expand Down
16 changes: 11 additions & 5 deletions test/protobuf/protoc/generator/enum_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,21 @@ defmodule Protobuf.Protoc.Generator.EnumTest do
assert [{compiled_mod, bytecode}] = Code.compile_string(msg)
assert inspect(compiled_mod) == module

assert msg =~ "defmodule #{module} do\n"
assert msg =~ "use Protobuf, enum: true, protoc_gen_elixir_version: \"#{Util.version()}\"\n"
assert msg == """
defmodule ProtobufProtocGeneratorEnumTestEnumFoo do
@moduledoc false

refute msg =~ "defstruct "
use Protobuf,
enum: true,
full_name: "ProtobufProtocGeneratorEnumTestEnumFoo",
protoc_gen_elixir_version: "#{Util.version()}"

assert msg =~ """
field :A, 0
field :B, 1
field :HAS_UNDERSCORES, 2
field :HAS_UNDERSCORES_X, 3
field :HAS_UNDERSCORES_, 4
end
"""

assert TestHelpers.get_type_spec_as_string(compiled_mod, bytecode, :t) ==
Expand Down Expand Up @@ -68,7 +72,9 @@ defmodule Protobuf.Protoc.Generator.EnumTest do
assert inspect(compiled_mod) == module

assert msg =~ "defmodule #{module} do\n"
assert msg =~ "use Protobuf, enum: true, protoc_gen_elixir_version: \"#{Util.version()}\"\n"

assert msg =~
"use Protobuf,\n enum: true,\n full_name: \"ProtobufProtocGeneratorEnumTestEnumFooDesc\",\n protoc_gen_elixir_version: \"#{Util.version()}\"\n\n"

refute msg =~ "defstruct "

Expand Down
24 changes: 16 additions & 8 deletions test/protobuf/protoc/generator/message_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,21 @@ defmodule Protobuf.Protoc.Generator.MessageTest do
alias Protobuf.TestHelpers

test "generate/2 has right name" do
ctx = %Context{}
ctx = %Context{package: "pkg.name"}
desc = %Google.Protobuf.DescriptorProto{name: "Foo"}
{[], [{_mod, msg}]} = Generator.generate(ctx, desc)
assert msg =~ "defmodule Foo do\n"
assert msg =~ "use Protobuf, protoc_gen_elixir_version: \"#{Util.version()}\"\n"
assert msg =~ "defmodule Pkg.Name.Foo do\n"

assert msg =~
"use Protobuf, full_name: \"pkg.name.Foo\", protoc_gen_elixir_version: \"#{Util.version()}\"\n"

assert [{compiled_mod, bytecode}] = Code.compile_string(msg)

assert TestHelpers.get_type_spec_as_string(compiled_mod, bytecode, :t) ==
Macro.to_string(
quote(
do:
t() :: %Foo{
t() :: %Pkg.Name.Foo{
__unknown_fields__: [Protobuf.unknown_field()]
}
)
Expand Down Expand Up @@ -81,7 +83,9 @@ defmodule Protobuf.Protoc.Generator.MessageTest do
}

{[], [{_mod, msg}]} = Generator.generate(ctx, desc)
assert msg =~ "use Protobuf, map: true, protoc_gen_elixir_version: \"#{Util.version()}\"\n"

assert msg =~
"use Protobuf, full_name: \"pkg.name.Foo\", map: true, protoc_gen_elixir_version: \"#{Util.version()}\"\n"
end

test "generate/2 has right fields" do
Expand Down Expand Up @@ -611,12 +615,16 @@ defmodule Protobuf.Protoc.Generator.MessageTest do
}

{[[{_mod, msg}]], _} = Generator.generate(ctx, desc)
assert msg =~ "defmodule Foo.Nested.EnumFoo do\n"
assert msg =~ "use Protobuf, enum: true, protoc_gen_elixir_version: \"#{Util.version()}\"\n"

assert msg =~ """
assert msg == """
defmodule Foo.Nested.EnumFoo do
@moduledoc false

use Protobuf, enum: true, full_name: "Foo.Nested.EnumFoo", protoc_gen_elixir_version: "#{Util.version()}"

field :a, 0
field :b, 1
end
"""
end

Expand Down
7 changes: 7 additions & 0 deletions test/protobuf/protoc/generator_integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,11 @@ defmodule Protobuf.Protoc.GeneratorIntegrationTest do
assert NoPackageMessage.NumberMappingEntry.__message_props__().map?
assert NoPackageMessage.decode(output) == input
end

test "full_name/0 returns expected value for generated modules from full_name.proto" do
assert Foo.Bar.Unit.full_name() == "foo.bar.Unit"
assert Foo.Bar.Message.full_name() == "foo.bar.Message"
assert Foo.Bar.Message.NestedMessage.full_name() == "foo.bar.Message.NestedMessage"
assert Foo.Bar.Message.NestedMessage.Kind.full_name() == "foo.bar.Message.NestedMessage.Kind"
end
end
34 changes: 34 additions & 0 deletions test/protobuf/protoc/proto/full_name.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// From: https://protobuf.com/docs/language-spec#fully-qualified-names
syntax = "proto3"; // Fully-qualified name
//----------------------
package foo.bar; // foo.bar
//
import "google/protobuf/descriptor.proto"; //
//
message Message { // foo.bar.Message
oneof id { // foo.bar.Message.id
string name = 1; // foo.bar.Message.name
uint64 num = 2; // foo.bar.Message.num
} //
message NestedMessage { // foo.bar.Message.NestedMessage
extend google.protobuf.MessageOptions { //
string fizz = 49999; // foo.bar.Message.NestedMessage.fizz
} //
option (NestedMessage.fizz) = "buzz"; //
enum Kind { // foo.bar.Message.NestedMessage.Kind
NULL = 0; // foo.bar.Message.NestedMessage.NULL
PRIMARY = 1; // foo.bar.Message.NestedMessage.PRIMARY
SECONDARY = 2; // foo.bar.Message.NestedMessage.SECONDARY
} //
Kind kind = 1; // foo.bar.Message.NestedMessage.kind
} //
NestedMessage extra = 3; // foo.bar.Message.extra
} //
//
enum Unit { // foo.bar.Unit
VOID = 0; // foo.bar.VOID
} //
//
service FooService { // foo.bar.FooService
rpc Bar(Message) returns (Message); // foo.bar.FooService.Bar
} //
1 change: 1 addition & 0 deletions test/protobuf/protoc/proto/test.proto
Original file line number Diff line number Diff line change
Expand Up @@ -153,3 +153,4 @@ enum MapEnum {
HELLO = 0;
WORLD = 2;
}

Loading