Skip to content
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
12 changes: 10 additions & 2 deletions lib/protobuf/protoc/generator.ex
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ defmodule Protobuf.Protoc.Generator do
@spec generate(Context.t(), %Google.Protobuf.FileDescriptorProto{}) ::
{term(), [Google.Protobuf.Compiler.CodeGeneratorResponse.File.t()]}
def generate(%Context{} = ctx, %Google.Protobuf.FileDescriptorProto{} = desc) do
ctx =
%{ctx | package: desc.package}
|> Protobuf.Protoc.Context.custom_file_options_from_file_desc(desc)

{package_level_extensions, module_definitions} = generate_module_definitions(ctx, desc)

files =
Expand All @@ -17,8 +21,12 @@ defmodule Protobuf.Protoc.Generator do
%Google.Protobuf.Compiler.CodeGeneratorResponse.File{name: file_name, content: content}
end)
else
# desc.name is the filename, ending in ".proto".
file_name = Path.rootname(desc.name) <> ".pb.ex"
base_module = Generator.Util.mod_name(ctx, [])
file_name =
case base_module do
"" -> Path.rootname(desc.name) <> ".pb.ex"
_ -> Macro.underscore(base_module) <> "/" <> Path.rootname(desc.name) <> ".pb.ex"
end

content =
module_definitions
Expand Down
26 changes: 13 additions & 13 deletions test/protobuf/protoc/cli_integration_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/user.pb.ex")
assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/foo/user.pb.ex")
assert mod == Foo.User
end

Expand All @@ -43,7 +43,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/user.pb.ex")
assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/foo/user.pb.ex")
assert mod == Foo.User

assert mod.transform_module() == TestMsg.TransformModule
Expand All @@ -58,7 +58,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/user.pb.ex")
assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/foo/user.pb.ex")
assert mod == Foo.User

assert %Google.Protobuf.DescriptorProto{} = descriptor = mod.descriptor()
Expand All @@ -74,7 +74,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

modules_and_docs = get_docs_and_clean_modules_on_exit("#{tmp_dir}/user.pb.ex")
modules_and_docs = get_docs_and_clean_modules_on_exit("#{tmp_dir}/foo/user.pb.ex")
assert [{Foo.User, docs}] = modules_and_docs
assert {:docs_v1, _, :elixir, _, module_doc, _, _} = docs
assert module_doc != :hidden
Expand All @@ -88,7 +88,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

modules_and_docs = get_docs_and_clean_modules_on_exit("#{tmp_dir}/user.pb.ex")
modules_and_docs = get_docs_and_clean_modules_on_exit("#{tmp_dir}/foo/user.pb.ex")

assert [{Foo.User, docs}] = modules_and_docs
assert {:docs_v1, _, :elixir, _, :hidden, _, _} = docs
Expand Down Expand Up @@ -122,7 +122,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/user.pb.ex")
assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/mypkg/foo/user.pb.ex")
assert mod == Mypkg.Foo.User
end

Expand Down Expand Up @@ -178,7 +178,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path
])

assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/timestamp_wrapper.pb.ex")
assert [mod] = compile_file_and_clean_modules_on_exit("#{tmp_dir}/my_type/timestamp_wrapper.pb.ex")

assert mod == MyType.TimestampWrapper
assert Map.fetch!(mod.__message_props__().field_props, 1).type == Google.Protobuf.Timestamp
Expand Down Expand Up @@ -244,13 +244,13 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
])

assert [Bugs.Base = base_mod] =
compile_file_and_clean_modules_on_exit("#{tmp_dir}/base.pb.ex")
compile_file_and_clean_modules_on_exit("#{tmp_dir}/bugs/base.pb.ex")

assert [Bugs.Ext1, Bugs.Ext1.PbExtension] =
compile_file_and_clean_modules_on_exit("#{tmp_dir}/ext1.pb.ex")
compile_file_and_clean_modules_on_exit("#{tmp_dir}/bugs/ext1.pb.ex")

assert [Bugs.Ext2, Bugs.Ext2.PbExtension] =
compile_file_and_clean_modules_on_exit("#{tmp_dir}/ext2.pb.ex")
compile_file_and_clean_modules_on_exit("#{tmp_dir}/bugs/ext2.pb.ex")

assert [Bugs.PbExtension] =
compile_file_and_clean_modules_on_exit("#{tmp_dir}/bugs/pb_extension.pb.ex")
Expand Down Expand Up @@ -343,8 +343,8 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
proto_path_two
])

elixir_one = File.read!("#{tmp_dir}/one.pb.ex")
elixir_two = File.read!("#{tmp_dir}/two.pb.ex")
elixir_one = File.read!("#{tmp_dir}/tests/one.pb.ex")
elixir_two = File.read!("#{tmp_dir}/tests/two.pb.ex")

assert elixir_one =~ """
defmodule Tests.One do
Expand Down Expand Up @@ -414,7 +414,7 @@ defmodule Protobuf.Protoc.CLIIntegrationTest do
])

assert [message_mod, extension_mod] =
compile_file_and_clean_modules_on_exit("#{tmp_dir}/extensions.pb.ex")
compile_file_and_clean_modules_on_exit("#{tmp_dir}/my_pkg/extensions.pb.ex")

assert %Google.Protobuf.MessageOptions{} = options = message_mod.descriptor().options
assert options.__pb_extensions__ == %{{extension_mod, :notes} => "This messge is cool"}
Expand Down
93 changes: 93 additions & 0 deletions test/protobuf/protoc/generator/file_path_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
defmodule Protobuf.Protoc.FilePathTest do
use ExUnit.Case, async: true

alias Google.Protobuf.{DescriptorProto, FileDescriptorProto}
alias Protobuf.Protoc.Context
alias Protobuf.Protoc.Generator

describe "generate/2 - file naming with module_prefix" do
test "file path does not respect module_prefix when one_file_per_module is false (default)" do
ctx = %Context{
module_prefix: "MyApp.V1",
package: nil,
syntax: :proto2,
one_file_per_module?: false,
dep_type_mapping: %{},
global_type_mapping: %{"example.proto" => %{}}
}

desc = %FileDescriptorProto{
name: "example.proto",
message_type: [
%DescriptorProto{
name: "Foo"
}
]
}

{_package_level_exts, files} = Generator.generate(ctx, desc)

assert length(files) == 1
file = List.first(files)

assert file.content =~ "defmodule MyApp.V1.Foo do"

assert file.name == "my_app/v1/example.pb.ex",
"File path should respect module_prefix. Expected 'my_app/v1/example.pb.ex' to match module 'MyApp.V1.Foo', but got '#{file.name}'"
end

test "file path respects module_prefix when one_file_per_module is true" do
ctx = %Context{
module_prefix: "MyApp.V1",
package: nil,
syntax: :proto2,
one_file_per_module?: true,
dep_type_mapping: %{},
global_type_mapping: %{"example.proto" => %{}}
}

desc = %FileDescriptorProto{
name: "example.proto",
message_type: [
%DescriptorProto{
name: "Foo"
}
]
}

{_package_level_exts, files} = Generator.generate(ctx, desc)

assert length(files) == 1
file = List.first(files)

assert file.name == "my_app/v1/foo.pb.ex",
"Expected 'my_app/v1/foo.pb.ex' but got '#{file.name}'"
end

test "file path should respect module_prefix with extensions" do
ctx = %Context{
module_prefix: "Protobuf.Protoc.ExtTest",
package: "ext",
syntax: :proto2,
one_file_per_module?: false,
dep_type_mapping: %{".ext.Foo" => %{type_name: "Protobuf.Protoc.ExtTest.Foo"}},
global_type_mapping: %{"extension.proto" => %{}}
}

desc = %FileDescriptorProto{
name: "extension.proto",
message_type: [
%DescriptorProto{name: "Foo"}
]
}

{_package_level_exts, files} = Generator.generate(ctx, desc)

assert length(files) == 1
file = List.first(files)

assert file.name == "protobuf/protoc/ext_test/extension.pb.ex",
"File path should respect module_prefix. Expected 'protobuf/protoc/ext_test/extension.pb.ex' to match module 'Protobuf.Protoc.ExtTest.*', but got '#{file.name}'"
end
end
end
38 changes: 35 additions & 3 deletions test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,41 @@ defmodule Protobuf.TestHelpers do
end

def read_generated_file(relative_path) do
[__DIR__, "../generated", relative_path]
|> Path.join()
|> File.read!()
base_path = [__DIR__, "../generated", relative_path] |> Path.join()
Copy link
Contributor Author

@yordis yordis Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made CI to pass 😭 Ideas welcomed


if File.exists?(base_path) do
File.read!(base_path)
else
filename = Path.basename(relative_path)
generated_dir = [__DIR__, "../generated"] |> Path.join()

case find_file_in_dir(generated_dir, filename) do
{:ok, path} -> File.read!(path)
:not_found -> File.read!(base_path)
end
end
end

defp find_file_in_dir(dir, filename) do
case File.ls(dir) do
{:ok, entries} ->
Enum.find_value(entries, :not_found, fn entry ->
path = Path.join(dir, entry)

cond do
entry == filename -> {:ok, path}
File.dir?(path) ->
case find_file_in_dir(path, filename) do
{:ok, found_path} -> {:ok, found_path}
:not_found -> nil
end
true -> nil
end
end)

{:error, _} ->
:not_found
end
end

def get_type_spec_as_string(module, bytecode, type)
Expand Down