Skip to content

Commit 3ba2d44

Browse files
committed
fix: handle FileOptions containing messages
Closes #228
1 parent d72d01e commit 3ba2d44

File tree

5 files changed

+68
-5
lines changed

5 files changed

+68
-5
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## 2.0.2
9+
10+
### Fixed
11+
- Handle FileOptions containing messages
12+
813
## 2.0.1
914

1015
### Fixed

lib/protox/parse.ex

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ defmodule Protox.Parse do
132132
_ ->
133133
# If FileOptions and other related message have been found, we also need
134134
# to compile their associated enums.
135-
{file_options_optimize_enum, other_enums} =
135+
{file_options_enums, other_enums} =
136136
Map.split(definition.enums_schemas, [
137137
Google.Protobuf.FeatureSet.EnumType,
138138
Google.Protobuf.FeatureSet.FieldPresence,
@@ -143,10 +143,19 @@ defmodule Protox.Parse do
143143
Google.Protobuf.FileOptions.OptimizeMode
144144
])
145145

146+
# FileOptions can also contain nested messages and enums, which we need to compile as well.
147+
nested_in_file_options = find_enums_and_messages(definition.messages_schemas, Google.Protobuf.FileOptions)
148+
messages_used_in_file_options = Map.take(definition.messages_schemas, nested_in_file_options.messages)
149+
enums_used_in_file_options = Map.take(definition.enums_schemas, nested_in_file_options.enums)
150+
151+
# Remove those messages and enums from the returned definition to avoid compiling them twice.
152+
other_messages = Map.drop(other_messages, nested_in_file_options.messages)
153+
other_enums = Map.drop(other_enums, nested_in_file_options.enums)
154+
146155
# Compile the needed modules.
147156
%Definition{
148-
messages_schemas: file_options_messages,
149-
enums_schemas: file_options_optimize_enum
157+
messages_schemas: Map.merge(file_options_messages, messages_used_in_file_options),
158+
enums_schemas: Map.merge(file_options_enums, enums_used_in_file_options)
150159
}
151160
|> Protox.Define.define()
152161
|> Code.eval_quoted()
@@ -155,7 +164,7 @@ defmodule Protox.Parse do
155164
# Also, we transform this FileOptions into a bare map so as to not depend
156165
# on the FileOptions type which is not necessary for the end user.
157166
other_messages =
158-
for {msg_name, msg} <- other_messages, into: %{} do
167+
for {msg_name, msg} when msg.file_options != nil <- other_messages, into: %{} do
159168
file_options =
160169
msg.file_options
161170
|> Protox.Google.Protobuf.FileOptions.encode!()
@@ -189,6 +198,23 @@ defmodule Protox.Parse do
189198
end
190199
end
191200

201+
defp find_enums_and_messages(messages_schemas, msg_name, acc \\ %{enums: [], messages: []}) do
202+
Enum.reduce(messages_schemas[msg_name].fields, acc, fn
203+
{_field_name, %Protox.Field{type: {:message, sub_msg_name}}}, acc ->
204+
find_enums_and_messages(
205+
messages_schemas,
206+
sub_msg_name,
207+
_new_acc = Map.update!(acc, :messages, &[sub_msg_name | &1])
208+
)
209+
210+
{_field_name, %Protox.Field{type: {:enum, sub_enum_name}}}, acc ->
211+
Map.update!(acc, :enums, &[sub_enum_name | &1])
212+
213+
_field, acc ->
214+
acc
215+
end)
216+
end
217+
192218
defp resolve_types(%Field{type: {:type_to_resolve, tname}} = field, enums) do
193219
if Map.has_key?(enums, tname) do
194220
%{field | type: {:enum, tname}}

test/file_options_test.exs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@ defmodule FileOptionsTest do
88
# However, if defined directly in the test file, it works fine.
99
use Protox,
1010
files: [
11-
"./test/samples/custom_file_options.proto"
11+
"./test/samples/custom_file_options.proto",
12+
"./test/samples/message_as_file_option.proto",
13+
"./test/samples/use_message_as_file_option.proto"
1214
]
1315

1416
test "Can read custom option from FileOptions" do
@@ -24,4 +26,10 @@ defmodule FileOptionsTest do
2426
bar_file_options = JavaBar.schema().file_options
2527
assert Map.get(bar_file_options, :java_package) == "com.bar"
2628
end
29+
30+
test "Can read custom option (which is a message) from FileOptions" do
31+
file_options = Protox.Test.DummyMessage.schema().file_options
32+
assert Map.has_key?(file_options, :msg_as_file_option)
33+
assert Map.get(file_options, :msg_as_file_option) == %Protox.Test.MsgAsFileOption{a: 42}
34+
end
2735
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
syntax = "proto3";
2+
3+
package protox.test;
4+
5+
import "google/protobuf/descriptor.proto";
6+
7+
extend google.protobuf.FileOptions {
8+
MsgAsFileOption msg_as_file_option = 1053;
9+
}
10+
11+
message MsgAsFileOption {
12+
int32 a = 1;
13+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
syntax = "proto3";
2+
3+
package protox.test;
4+
5+
import "message_as_file_option.proto";
6+
7+
option (protox.test.msg_as_file_option) = {
8+
a: 42
9+
};
10+
11+
message DummyMessage{}

0 commit comments

Comments
 (0)