Skip to content

Commit 64ad8e5

Browse files
authored
rework state and ops, store descriptor once (#68)
1 parent 0332149 commit 64ad8e5

File tree

9 files changed

+92
-84
lines changed

9 files changed

+92
-84
lines changed

lib/grpc_reflection/server.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,12 @@ defmodule GrpcReflection.Server do
3333
end
3434

3535
@doc """
36-
Get the reflection reponse containing the given symbol, if it is exposed by a configured service
36+
Get the reflection filename for the given symbol, if it is known
3737
"""
38-
@spec get_by_symbol(binary()) ::
39-
{:ok, GrpcReflection.Server.descriptor_t()} | {:error, binary}
40-
def get_by_symbol(symbol) do
41-
Service.get_by_symbol(@cfg, symbol)
38+
@spec get_filename_by_symbol(binary()) ::
39+
{:ok, String.t()} | {:error, binary}
40+
def get_filename_by_symbol(symbol) do
41+
Service.get_filename_by_symbol(@cfg, symbol)
4242
end
4343

4444
@doc """

lib/grpc_reflection/server/process.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ defmodule GrpcReflection.Server.Process do
1111
end
1212

1313
def reflect(state_mod, {:file_containing_symbol, symbol}) do
14-
with {:ok, description} <- state_mod.get_by_symbol(symbol) do
15-
{:ok, {:file_descriptor_response, description}}
14+
with {:ok, filename} <- state_mod.get_filename_by_symbol(symbol),
15+
{:ok, descriptor} <- state_mod.get_by_filename(filename) do
16+
{:ok, {:file_descriptor_response, descriptor}}
1617
end
1718
end
1819

lib/grpc_reflection/service.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ defmodule GrpcReflection.Service do
1111
defdelegate put_state(cfg, state), to: Agent
1212

1313
defdelegate list_services(cfg), to: Agent
14-
defdelegate get_by_symbol(cfg, symbol), to: Agent
14+
defdelegate get_filename_by_symbol(cfg, symbol), to: Agent
1515
defdelegate get_by_filename(cfg, filename), to: Agent
1616
defdelegate get_by_extension(cfg, containing_type), to: Agent
1717
defdelegate get_extension_numbers_by_type(cfg, mod), to: Agent

lib/grpc_reflection/service/agent.ex

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ defmodule GrpcReflection.Service.Agent do
3030
Agent.get(name, &State.lookup_services/1)
3131
end
3232

33-
@spec get_by_symbol(cfg_t(), binary()) :: {:ok, State.descriptor_t()} | {:error, binary}
34-
def get_by_symbol(cfg, symbol) do
33+
@spec get_filename_by_symbol(cfg_t(), binary()) ::
34+
{:ok, State.descriptor_t()} | {:error, binary}
35+
def get_filename_by_symbol(cfg, symbol) do
3536
name = start_agent_on_first_call(cfg)
3637
Agent.get(name, &State.lookup_symbol(symbol, &1))
3738
end

lib/grpc_reflection/service/builder.ex

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@ defmodule GrpcReflection.Service.Builder do
99
def build_reflection_tree(services) do
1010
with :ok <- Util.validate_services(services) do
1111
tree =
12-
Enum.reduce(services, State.new(services), fn service, state ->
12+
services
13+
|> Enum.reduce(State.new(), fn service, state ->
1314
new_state = process_service(service)
1415
State.merge(state, new_state)
1516
end)
17+
|> State.group_symbols_by_namespace()
1618

1719
{:ok, tree}
1820
end
@@ -22,13 +24,14 @@ defmodule GrpcReflection.Service.Builder do
2224
service_name = service.__meta__(:name)
2325
service_response = build_response(service_name, service)
2426

25-
State.new()
26-
|> State.add_symbols(%{service_name => service_response})
27-
|> State.add_files(%{(service_name <> ".proto") => service_response})
28-
|> trace_service_refs(service)
27+
[service]
28+
|> State.new()
29+
|> State.add_file(service_response)
30+
|> State.add_symbol(service_name, service_response.name)
31+
|> trace_service_refs(service, service_response.name)
2932
end
3033

31-
defp trace_service_refs(state, module) do
34+
defp trace_service_refs(state, module, module_filename) do
3235
service_name = module.__meta__(:name)
3336
methods = get_descriptor(module).method
3437

@@ -40,23 +43,21 @@ defmodule GrpcReflection.Service.Builder do
4043
Enum.find(methods, fn method -> method.name == to_string(function) end)
4144

4245
call_symbol = service_name <> "." <> to_string(function)
43-
call_response = build_response(service_name, module)
46+
4447
req_symbol = Util.trim_symbol(req_symbol)
4548
req_response = build_response(req_symbol, request)
49+
req_filename = req_symbol <> ".proto"
4650
resp_symbol = Util.trim_symbol(resp_symbol)
4751
resp_response = build_response(resp_symbol, response)
52+
resp_filename = resp_symbol <> ".proto"
4853

4954
state
5055
|> Extensions.add_extensions(service_name, module)
51-
|> State.add_symbols(%{
52-
call_symbol => call_response,
53-
req_symbol => req_response,
54-
resp_symbol => resp_response
55-
})
56-
|> State.add_files(%{
57-
(req_symbol <> ".proto") => req_response,
58-
(resp_symbol <> ".proto") => resp_response
59-
})
56+
|> State.add_file(req_response)
57+
|> State.add_file(resp_response)
58+
|> State.add_symbol(call_symbol, module_filename)
59+
|> State.add_symbol(req_symbol, req_filename)
60+
|> State.add_symbol(resp_symbol, resp_filename)
6061
|> Extensions.add_extensions(req_symbol, request)
6162
|> Extensions.add_extensions(resp_symbol, response)
6263
|> trace_message_refs(req_symbol, request)
@@ -103,8 +104,8 @@ defmodule GrpcReflection.Service.Builder do
103104

104105
state
105106
|> Extensions.add_extensions(symbol, mod)
106-
|> State.add_symbols(%{symbol => response})
107-
|> State.add_files(%{(symbol <> ".proto") => response})
107+
|> State.add_file(response)
108+
|> State.add_symbol(symbol, response.name)
108109
|> trace_message_refs(symbol, mod)
109110
end)
110111
end

lib/grpc_reflection/service/builder/extensions.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule GrpcReflection.Service.Builder.Extensions do
1010
case process_extensions(module, symbol, extension_file, module.descriptor()) do
1111
{:ok, {extension_numbers, extension_payload}} ->
1212
state
13-
|> State.add_files(%{extension_file => extension_payload})
13+
|> State.add_file(extension_payload)
1414
|> State.add_extensions(%{symbol => extension_numbers})
1515

1616
:ignore ->

lib/grpc_reflection/service/state.ex

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,14 @@ defmodule GrpcReflection.Service.State do
33

44
defstruct services: [], files: %{}, symbols: %{}, extensions: %{}
55

6-
@type descriptor_t :: GrpcReflection.Server.descriptor_t()
6+
@type descriptor_t :: Google.Protobuf.FileDescriptorProto.t()
7+
@type symbol_t :: %{optional(binary()) => binary()}
78
@type entry_t :: %{optional(binary()) => descriptor_t()}
89

910
@type t :: %__MODULE__{
1011
services: list(module()),
1112
files: entry_t(),
12-
symbols: entry_t(),
13+
symbols: symbol_t(),
1314
extensions: %{optional(binary()) => list(integer())}
1415
}
1516

@@ -26,6 +27,7 @@ defmodule GrpcReflection.Service.State do
2627
}
2728
end
2829

30+
# merge two maps, but raise if we have duplicate keys with different values
2931
defp merge_map!(type, current_map, incoming_map) do
3032
Map.merge(current_map, incoming_map, fn
3133
_key, value, value ->
@@ -36,14 +38,27 @@ defmodule GrpcReflection.Service.State do
3638
end)
3739
end
3840

39-
@spec add_files(t(), entry_t()) :: t()
40-
def add_files(%__MODULE__{} = state, files) do
41-
%{state | files: Map.merge(files, state.files)}
41+
# add an item to a map, but raise if the key exists with a different value
42+
defp put_map!(type, current_map, key, value) do
43+
case current_map[key] do
44+
nil -> Map.put(current_map, key, value)
45+
^value -> current_map
46+
_ -> raise "#{type} Conflict detected: key #{key} present with multiple values"
47+
end
4248
end
4349

44-
@spec add_symbols(t(), entry_t()) :: t()
45-
def add_symbols(%__MODULE__{} = state, symbols) do
46-
%{state | symbols: Map.merge(symbols, state.symbols)}
50+
@spec add_file(t(), descriptor_t()) :: t()
51+
def add_file(%__MODULE__{} = state, descriptor) do
52+
%{state | files: put_map!("Files", state.files, descriptor.name, descriptor)}
53+
end
54+
55+
@spec add_symbol(t(), String.t(), String.t()) :: t()
56+
def add_symbol(%__MODULE__{files: files} = state, symbol, filename) do
57+
if files[filename] == nil do
58+
raise "Symbol #{symbol} added for file that doesn't exist: #{filename}"
59+
end
60+
61+
%{state | symbols: put_map!("Symbols", state.symbols, symbol, filename)}
4762
end
4863

4964
def add_extensions(%__MODULE__{} = state, extensions) do
@@ -100,4 +115,10 @@ defmodule GrpcReflection.Service.State do
100115
{:error, "extension numbers not found"}
101116
end
102117
end
118+
119+
def group_symbols_by_namespace(%__MODULE__{} = state) do
120+
# group symbols by namespace and combine
121+
# IO.inspect(state)
122+
state
123+
end
103124
end

test/service/builder_test.exs

Lines changed: 14 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ defmodule GrpcReflection.Service.BuilderTest do
1616
"google.protobuf.Timestamp.proto",
1717
"testserviceV3.Enum.proto",
1818
"testserviceV3.TestReply.proto",
19-
"testserviceV3.TestRequest.GEntry.proto",
20-
"testserviceV3.TestRequest.Payload.Location.proto",
2119
"testserviceV3.TestRequest.Payload.proto",
2220
"testserviceV3.TestRequest.Token.proto",
2321
"testserviceV3.TestRequest.proto",
@@ -39,7 +37,7 @@ defmodule GrpcReflection.Service.BuilderTest do
3937
"testserviceV3.TestService.CallFunction"
4038
]
4139

42-
Enum.each(Map.values(tree.files) ++ Map.values(tree.symbols), fn payload ->
40+
Enum.each(Map.values(tree.files), fn payload ->
4341
assert payload.syntax == "proto3"
4442
end)
4543
end
@@ -53,7 +51,6 @@ defmodule GrpcReflection.Service.BuilderTest do
5351
"google.protobuf.Timestamp.proto",
5452
"testserviceV2.Enum.proto",
5553
"testserviceV2.TestReply.proto",
56-
"testserviceV2.TestRequest.GEntry.proto",
5754
"testserviceV2.TestRequest.proto",
5855
"testserviceV2.TestRequestExtension.proto",
5956
"testserviceV2.TestService.proto"
@@ -77,7 +74,7 @@ defmodule GrpcReflection.Service.BuilderTest do
7774
# this is a bitstring that may contain whitespace characters
7875
assert extensions |> to_string() |> String.trim() == ""
7976

80-
Enum.each(Map.values(tree.files) ++ Map.values(tree.symbols), fn
77+
Enum.each(Map.values(tree.files), fn
8178
%{name: "google" <> _, syntax: syntax} -> assert syntax == "proto3"
8279
%{name: _, syntax: syntax} -> assert syntax == "proto2"
8380
end)
@@ -87,8 +84,7 @@ defmodule GrpcReflection.Service.BuilderTest do
8784
assert {:ok, tree} = Builder.build_reflection_tree([EmptyService.Service])
8885
assert %State{services: [EmptyService.Service]} = tree
8986

90-
(Map.values(tree.files) ++ Map.values(tree.symbols))
91-
|> Enum.each(fn payload ->
87+
Enum.each(Map.values(tree.files), fn payload ->
9288
# empty services default to proto2
9389
assert payload.syntax == "proto2"
9490
assert payload.dependency == []
@@ -110,29 +106,17 @@ defmodule GrpcReflection.Service.BuilderTest do
110106
assert {:ok, tree} = Builder.build_reflection_tree([HLW.TestService.Service])
111107
assert %State{services: [HLW.TestService.Service]} = tree
112108

113-
names =
114-
(Map.values(tree.files) ++ Map.values(tree.symbols))
115-
|> Enum.map(& &1.name)
116-
117-
assert names ==
118-
[
119-
"google.protobuf.Any.proto",
120-
"google.protobuf.Timestamp.proto",
121-
"testserviceV2.Enum.proto",
122-
"testserviceV2.TestReply.proto",
123-
"testserviceV2.TestRequest.proto",
124-
"testserviceV2.TestRequest.proto",
125-
"testserviceV2.TestRequestExtension.proto",
126-
"testserviceV2.TestService.proto",
127-
"google.protobuf.Any.proto",
128-
"google.protobuf.Timestamp.proto",
129-
"testserviceV2.Enum.proto",
130-
"testserviceV2.TestReply.proto",
131-
"testserviceV2.TestRequest.proto",
132-
"testserviceV2.TestRequest.proto",
133-
"testserviceV2.TestService.proto",
134-
"testserviceV2.TestService.proto"
135-
]
109+
names = Enum.map(Map.values(tree.files), & &1.name)
110+
111+
assert names == [
112+
"google.protobuf.Any.proto",
113+
"google.protobuf.Timestamp.proto",
114+
"testserviceV2.Enum.proto",
115+
"testserviceV2.TestReply.proto",
116+
"testserviceV2.TestRequest.proto",
117+
"testserviceV2.TestRequestExtension.proto",
118+
"testserviceV2.TestService.proto"
119+
]
136120
end
137121

138122
test "handles a non-service module" do

test/service/server_test.exs

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ defmodule GrpcReflection.ServerTest do
1010
test "adding a service changes responses" do
1111
assert Service.list_services() == []
1212

13-
assert Service.get_by_symbol("helloworld.Greeter") ==
13+
assert Service.get_filename_by_symbol("helloworld.Greeter") ==
1414
{:error, "symbol not found"}
1515

1616
assert Service.get_by_filename("helloworld.Greeter.proto") ==
@@ -20,11 +20,11 @@ defmodule GrpcReflection.ServerTest do
2020

2121
assert Service.list_services() == ["helloworld.Greeter"]
2222

23-
assert {:ok, descriptor} =
24-
Service.get_by_symbol("helloworld.Greeter")
23+
assert {:ok, filename} =
24+
Service.get_filename_by_symbol("helloworld.Greeter")
2525

26-
assert {:ok, ^descriptor} =
27-
Service.get_by_filename("helloworld.Greeter.proto")
26+
assert {:ok, %Google.Protobuf.FileDescriptorProto{name: ^filename}} =
27+
Service.get_by_filename(filename)
2828
end
2929

3030
describe "reflection state testing" do
@@ -45,27 +45,27 @@ defmodule GrpcReflection.ServerTest do
4545
end
4646

4747
test "method files return service descriptors" do
48-
assert {:ok, descriptor} =
49-
Service.get_by_symbol("helloworld.Greeter")
48+
assert {:ok, filename} =
49+
Service.get_filename_by_symbol("helloworld.Greeter")
5050

51-
assert {:ok, ^descriptor} =
52-
Service.get_by_symbol("helloworld.Greeter.SayHello")
51+
assert {:ok, %Google.Protobuf.FileDescriptorProto{name: ^filename}} =
52+
Service.get_by_filename(filename)
5353
end
5454

5555
test "describing a type returns the type" do
56-
assert {:ok, descriptor} =
57-
Service.get_by_symbol("helloworld.HelloRequest")
56+
assert {:ok, filename} =
57+
Service.get_filename_by_symbol("helloworld.HelloRequest")
5858

59-
assert {:ok, ^descriptor} =
60-
Service.get_by_filename("helloworld.HelloRequest.proto")
59+
assert {:ok, %Google.Protobuf.FileDescriptorProto{name: ^filename}} =
60+
Service.get_by_filename(filename)
6161
end
6262

6363
test "type with leading period still resolves" do
64-
assert {:ok, descriptor} =
65-
Service.get_by_symbol(".helloworld.HelloRequest")
64+
assert {:ok, filename} =
65+
Service.get_filename_by_symbol(".helloworld.HelloRequest")
6666

67-
assert {:ok, ^descriptor} =
68-
Service.get_by_filename("helloworld.HelloRequest.proto")
67+
assert {:ok, %Google.Protobuf.FileDescriptorProto{name: ^filename}} =
68+
Service.get_by_filename(filename)
6969
end
7070
end
7171
end

0 commit comments

Comments
 (0)