Skip to content

Commit 100cc17

Browse files
committed
add logic and some tests for bundling descriptors
1 parent 64ad8e5 commit 100cc17

File tree

3 files changed

+133
-3
lines changed

3 files changed

+133
-3
lines changed

lib/grpc_reflection/service/builder/util.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ defmodule GrpcReflection.Service.Builder.Util do
163163
[]
164164
end
165165

166+
# in gRPC, the leading "." signifies it is a FQDN
167+
# we trim it and assume everything is a FQDN
168+
# it works so far, but there may be corner cases
166169
def trim_symbol("." <> symbol), do: symbol
167170
def trim_symbol(symbol), do: symbol
168171
end

lib/grpc_reflection/service/state.ex

Lines changed: 67 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,72 @@ defmodule GrpcReflection.Service.State do
117117
end
118118

119119
def group_symbols_by_namespace(%__MODULE__{} = state) do
120-
# group symbols by namespace and combine
121-
# IO.inspect(state)
122-
state
120+
state.symbols
121+
|> Map.keys()
122+
|> Enum.group_by(&GrpcReflection.Service.Builder.Util.get_package(&1))
123+
|> Enum.reduce(state, fn {package, symbols}, state_acc ->
124+
# each symbol in symbols is in the same package
125+
# we will combine their files into a single file, and update them to
126+
# reference this new file
127+
128+
# Step 1: Collect descriptors to be combined
129+
symbol_files = Enum.map(symbols, &state.symbols[&1])
130+
files_to_combine = state.files |> Map.take(symbol_files) |> Map.values()
131+
132+
# Step 2: Combine the descriptors
133+
combined_file =
134+
Enum.reduce(
135+
files_to_combine,
136+
%Google.Protobuf.FileDescriptorProto{
137+
package: package,
138+
name: package <> ".proto"
139+
},
140+
fn descriptor, acc ->
141+
%{
142+
acc
143+
| syntax: descriptor.syntax,
144+
message_type: acc.message_type ++ descriptor.message_type,
145+
service: acc.service ++ descriptor.service,
146+
enum_type: acc.enum_type ++ descriptor.enum_type,
147+
dependency: acc.dependency ++ descriptor.dependency,
148+
extension: acc.extension ++ descriptor.extension
149+
}
150+
end
151+
)
152+
153+
# Step 3: remove internal dependency refs
154+
cleaned_file =
155+
%{combined_file | dependency: combined_file.dependency -- symbol_files}
156+
157+
# Step 4: rework state around combined descriptor
158+
# removing and re-adding symbols pointing to combined file
159+
# removing combined file descriptors
160+
# editing existing descriptors for relevant dependency entries
161+
# add combined file descriptor
162+
%{
163+
state_acc
164+
| symbols:
165+
state.symbols
166+
|> Map.drop(symbols)
167+
|> Map.merge(Map.new(symbols, &{&1, cleaned_file.name})),
168+
files:
169+
state.files
170+
|> Map.drop(symbol_files)
171+
|> Map.new(fn {filename, descriptor} ->
172+
if Enum.any?(descriptor.dependency, &Enum.member?(symbol_files, &1)) do
173+
{
174+
filename,
175+
%{
176+
descriptor
177+
| dependency: (descriptor.dependency -- symbol_files) ++ [cleaned_file.name]
178+
}
179+
}
180+
else
181+
{filename, descriptor}
182+
end
183+
end)
184+
|> Map.put(cleaned_file.name, cleaned_file)
185+
}
186+
end)
123187
end
124188
end

test/service/state_test.exs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,67 @@ defmodule GrpcReflection.Service.StateTest do
5555
fn -> State.merge(state1, state2) end
5656
end
5757
end
58+
59+
describe "group_symbols_by_namespace" do
60+
setup do
61+
state_with_recursion = %State{
62+
services: ["Service1", "Service2"],
63+
files: %{
64+
"file1.proto" => %Google.Protobuf.FileDescriptorProto{
65+
name: "file1.proto",
66+
package: ".common.path",
67+
dependency: ["file2.proto"],
68+
service: ["A"],
69+
message_type: [%Google.Protobuf.DescriptorProto{name: "Symbol_a"}],
70+
syntax: "proto2"
71+
},
72+
"file2.proto" => %Google.Protobuf.FileDescriptorProto{
73+
name: "file2.proto",
74+
package: ".common.path",
75+
dependency: ["file1.proto"],
76+
service: ["B"],
77+
message_type: [%Google.Protobuf.DescriptorProto{name: "Symbol_b"}],
78+
syntax: "proto2"
79+
},
80+
"file3.proto" => %Google.Protobuf.FileDescriptorProto{
81+
name: "file3.proto",
82+
package: ".other.path",
83+
dependency: ["file1.proto", "file2.proto"],
84+
service: ["C"],
85+
message_type: [%Google.Protobuf.DescriptorProto{name: "Symbol_c"}],
86+
syntax: "proto2"
87+
}
88+
},
89+
symbols: %{
90+
"common.path.Symbol_a" => "file1.proto",
91+
"common.path.Symbol_b" => "file2.proto"
92+
}
93+
}
94+
95+
%{
96+
state: State.group_symbols_by_namespace(state_with_recursion)
97+
}
98+
end
99+
100+
test "should maintain all symbols", %{state: state} do
101+
assert Map.keys(state.symbols) == ["common.path.Symbol_a", "common.path.Symbol_b"]
102+
end
103+
104+
test "should reduce and update files", %{state: state} do
105+
assert [combined_file, other_file] = Map.values(state.files)
106+
# combined file is present as we expect
107+
assert combined_file.dependency == []
108+
assert combined_file.name == "common.path.proto"
109+
# referencing file is updated as we expect
110+
assert other_file.dependency == ["common.path.proto"]
111+
assert other_file.name == "file3.proto"
112+
end
113+
114+
test "should combine descriptors", %{state: state} do
115+
file = state.files["common.path.proto"]
116+
symbols = Enum.map(file.message_type, & &1.name)
117+
assert symbols == ["Symbol_a", "Symbol_b"]
118+
assert file.service == ["A", "B"]
119+
end
120+
end
58121
end

0 commit comments

Comments
 (0)