From 100cc1705ff0c6527d16f4b9397197d38c01fb47 Mon Sep 17 00:00:00 2001 From: mjheilmann Date: Fri, 28 Nov 2025 22:48:19 -0500 Subject: [PATCH 1/6] add logic and some tests for bundling descriptors --- lib/grpc_reflection/service/builder/util.ex | 3 + lib/grpc_reflection/service/state.ex | 70 ++++++++++++++++++++- test/service/state_test.exs | 63 +++++++++++++++++++ 3 files changed, 133 insertions(+), 3 deletions(-) diff --git a/lib/grpc_reflection/service/builder/util.ex b/lib/grpc_reflection/service/builder/util.ex index 89f0740..7236571 100644 --- a/lib/grpc_reflection/service/builder/util.ex +++ b/lib/grpc_reflection/service/builder/util.ex @@ -163,6 +163,9 @@ defmodule GrpcReflection.Service.Builder.Util do [] end + # in gRPC, the leading "." signifies it is a FQDN + # we trim it and assume everything is a FQDN + # it works so far, but there may be corner cases def trim_symbol("." <> symbol), do: symbol def trim_symbol(symbol), do: symbol end diff --git a/lib/grpc_reflection/service/state.ex b/lib/grpc_reflection/service/state.ex index d87b33e..613ac67 100644 --- a/lib/grpc_reflection/service/state.ex +++ b/lib/grpc_reflection/service/state.ex @@ -117,8 +117,72 @@ defmodule GrpcReflection.Service.State do end def group_symbols_by_namespace(%__MODULE__{} = state) do - # group symbols by namespace and combine - # IO.inspect(state) - state + state.symbols + |> Map.keys() + |> Enum.group_by(&GrpcReflection.Service.Builder.Util.get_package(&1)) + |> Enum.reduce(state, fn {package, symbols}, state_acc -> + # each symbol in symbols is in the same package + # we will combine their files into a single file, and update them to + # reference this new file + + # Step 1: Collect descriptors to be combined + symbol_files = Enum.map(symbols, &state.symbols[&1]) + files_to_combine = state.files |> Map.take(symbol_files) |> Map.values() + + # Step 2: Combine the descriptors + combined_file = + Enum.reduce( + files_to_combine, + %Google.Protobuf.FileDescriptorProto{ + package: package, + name: package <> ".proto" + }, + fn descriptor, acc -> + %{ + acc + | syntax: descriptor.syntax, + message_type: acc.message_type ++ descriptor.message_type, + service: acc.service ++ descriptor.service, + enum_type: acc.enum_type ++ descriptor.enum_type, + dependency: acc.dependency ++ descriptor.dependency, + extension: acc.extension ++ descriptor.extension + } + end + ) + + # Step 3: remove internal dependency refs + cleaned_file = + %{combined_file | dependency: combined_file.dependency -- symbol_files} + + # Step 4: rework state around combined descriptor + # removing and re-adding symbols pointing to combined file + # removing combined file descriptors + # editing existing descriptors for relevant dependency entries + # add combined file descriptor + %{ + state_acc + | symbols: + state.symbols + |> Map.drop(symbols) + |> Map.merge(Map.new(symbols, &{&1, cleaned_file.name})), + files: + state.files + |> Map.drop(symbol_files) + |> Map.new(fn {filename, descriptor} -> + if Enum.any?(descriptor.dependency, &Enum.member?(symbol_files, &1)) do + { + filename, + %{ + descriptor + | dependency: (descriptor.dependency -- symbol_files) ++ [cleaned_file.name] + } + } + else + {filename, descriptor} + end + end) + |> Map.put(cleaned_file.name, cleaned_file) + } + end) end end diff --git a/test/service/state_test.exs b/test/service/state_test.exs index f5ec0d1..0336f32 100644 --- a/test/service/state_test.exs +++ b/test/service/state_test.exs @@ -55,4 +55,67 @@ defmodule GrpcReflection.Service.StateTest do fn -> State.merge(state1, state2) end end end + + describe "group_symbols_by_namespace" do + setup do + state_with_recursion = %State{ + services: ["Service1", "Service2"], + files: %{ + "file1.proto" => %Google.Protobuf.FileDescriptorProto{ + name: "file1.proto", + package: ".common.path", + dependency: ["file2.proto"], + service: ["A"], + message_type: [%Google.Protobuf.DescriptorProto{name: "Symbol_a"}], + syntax: "proto2" + }, + "file2.proto" => %Google.Protobuf.FileDescriptorProto{ + name: "file2.proto", + package: ".common.path", + dependency: ["file1.proto"], + service: ["B"], + message_type: [%Google.Protobuf.DescriptorProto{name: "Symbol_b"}], + syntax: "proto2" + }, + "file3.proto" => %Google.Protobuf.FileDescriptorProto{ + name: "file3.proto", + package: ".other.path", + dependency: ["file1.proto", "file2.proto"], + service: ["C"], + message_type: [%Google.Protobuf.DescriptorProto{name: "Symbol_c"}], + syntax: "proto2" + } + }, + symbols: %{ + "common.path.Symbol_a" => "file1.proto", + "common.path.Symbol_b" => "file2.proto" + } + } + + %{ + state: State.group_symbols_by_namespace(state_with_recursion) + } + end + + test "should maintain all symbols", %{state: state} do + assert Map.keys(state.symbols) == ["common.path.Symbol_a", "common.path.Symbol_b"] + end + + test "should reduce and update files", %{state: state} do + assert [combined_file, other_file] = Map.values(state.files) + # combined file is present as we expect + assert combined_file.dependency == [] + assert combined_file.name == "common.path.proto" + # referencing file is updated as we expect + assert other_file.dependency == ["common.path.proto"] + assert other_file.name == "file3.proto" + end + + test "should combine descriptors", %{state: state} do + file = state.files["common.path.proto"] + symbols = Enum.map(file.message_type, & &1.name) + assert symbols == ["Symbol_a", "Symbol_b"] + assert file.service == ["A", "B"] + end + end end From b2302afc61e0328ef6aa204ab1798a992a88dda1 Mon Sep 17 00:00:00 2001 From: mjheilmann Date: Fri, 28 Nov 2025 22:57:13 -0500 Subject: [PATCH 2/6] update tests for combined payloads --- lib/grpc_reflection/service/state.ex | 10 +++++----- test/integration/v1_reflection_test.exs | 15 ++++++++------- test/integration/v1alpha_reflection_test.exs | 15 ++++++++------- test/service/builder_test.exs | 17 +++-------------- 4 files changed, 24 insertions(+), 33 deletions(-) diff --git a/lib/grpc_reflection/service/state.ex b/lib/grpc_reflection/service/state.ex index 613ac67..c8144a8 100644 --- a/lib/grpc_reflection/service/state.ex +++ b/lib/grpc_reflection/service/state.ex @@ -141,11 +141,11 @@ defmodule GrpcReflection.Service.State do %{ acc | syntax: descriptor.syntax, - message_type: acc.message_type ++ descriptor.message_type, - service: acc.service ++ descriptor.service, - enum_type: acc.enum_type ++ descriptor.enum_type, - dependency: acc.dependency ++ descriptor.dependency, - extension: acc.extension ++ descriptor.extension + message_type: Enum.uniq(acc.message_type ++ descriptor.message_type), + service: Enum.uniq(acc.service ++ descriptor.service), + enum_type: Enum.uniq(acc.enum_type ++ descriptor.enum_type), + dependency: Enum.uniq(acc.dependency ++ descriptor.dependency), + extension: Enum.uniq(acc.extension ++ descriptor.extension) } end ) diff --git a/test/integration/v1_reflection_test.exs b/test/integration/v1_reflection_test.exs index ea5d94f..ad04a1a 100644 --- a/test/integration/v1_reflection_test.exs +++ b/test/integration/v1_reflection_test.exs @@ -74,7 +74,7 @@ defmodule GrpcReflection.V1ReflectionTest do test "describing a nested type returns the root type", ctx do message = {:file_containing_symbol, "testserviceV3.TestRequest.Payload"} assert {:ok, response} = run_request(message, ctx) - assert response.name == "testserviceV3.TestRequest.proto" + assert response.name == "testserviceV3.proto" end test "type with leading period still resolves", ctx do @@ -167,7 +167,7 @@ defmodule GrpcReflection.V1ReflectionTest do end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.TestReply.proto" + filename = "testserviceV3.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -175,21 +175,22 @@ defmodule GrpcReflection.V1ReflectionTest do assert response.dependency == [ "google.protobuf.Timestamp.proto", - "google.protobuf.StringValue.proto" + "google.protobuf.StringValue.proto", + "google.protobuf.Any.proto" ] end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.TestRequest.proto" + filename = "testserviceV3.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" assert response.dependency == [ - "testserviceV3.Enum.proto", - "google.protobuf.Any.proto", - "google.protobuf.StringValue.proto" + "google.protobuf.Timestamp.proto", + "google.protobuf.StringValue.proto", + "google.protobuf.Any.proto" ] end end diff --git a/test/integration/v1alpha_reflection_test.exs b/test/integration/v1alpha_reflection_test.exs index 0bd5092..20253a2 100644 --- a/test/integration/v1alpha_reflection_test.exs +++ b/test/integration/v1alpha_reflection_test.exs @@ -74,7 +74,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do test "describing a nested type returns the root type", ctx do message = {:file_containing_symbol, "testserviceV3.TestRequest.Payload"} assert {:ok, response} = run_request(message, ctx) - assert response.name == "testserviceV3.TestRequest.proto" + assert response.name == "testserviceV3.proto" end test "type with leading period still resolves", ctx do @@ -167,7 +167,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.TestReply.proto" + filename = "testserviceV3.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -175,21 +175,22 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert response.dependency == [ "google.protobuf.Timestamp.proto", - "google.protobuf.StringValue.proto" + "google.protobuf.StringValue.proto", + "google.protobuf.Any.proto" ] end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.TestRequest.proto" + filename = "testserviceV3.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" assert response.dependency == [ - "testserviceV3.Enum.proto", - "google.protobuf.Any.proto", - "google.protobuf.StringValue.proto" + "google.protobuf.Timestamp.proto", + "google.protobuf.StringValue.proto", + "google.protobuf.Any.proto" ] end end diff --git a/test/service/builder_test.exs b/test/service/builder_test.exs index 40e8129..2b605e1 100644 --- a/test/service/builder_test.exs +++ b/test/service/builder_test.exs @@ -14,12 +14,7 @@ defmodule GrpcReflection.Service.BuilderTest do "google.protobuf.Any.proto", "google.protobuf.StringValue.proto", "google.protobuf.Timestamp.proto", - "testserviceV3.Enum.proto", - "testserviceV3.TestReply.proto", - "testserviceV3.TestRequest.Payload.proto", - "testserviceV3.TestRequest.Token.proto", - "testserviceV3.TestRequest.proto", - "testserviceV3.TestService.proto" + "testserviceV3.proto" ] assert Map.keys(tree.symbols) == [ @@ -49,11 +44,8 @@ defmodule GrpcReflection.Service.BuilderTest do assert Map.keys(tree.files) == [ "google.protobuf.Any.proto", "google.protobuf.Timestamp.proto", - "testserviceV2.Enum.proto", - "testserviceV2.TestReply.proto", - "testserviceV2.TestRequest.proto", "testserviceV2.TestRequestExtension.proto", - "testserviceV2.TestService.proto" + "testserviceV2.proto" ] assert Map.keys(tree.symbols) == [ @@ -111,11 +103,8 @@ defmodule GrpcReflection.Service.BuilderTest do assert names == [ "google.protobuf.Any.proto", "google.protobuf.Timestamp.proto", - "testserviceV2.Enum.proto", - "testserviceV2.TestReply.proto", - "testserviceV2.TestRequest.proto", "testserviceV2.TestRequestExtension.proto", - "testserviceV2.TestService.proto" + "testserviceV2.proto" ] end From 8333e21d9231027613e9149f5de07f0b381eeeec Mon Sep 17 00:00:00 2001 From: mjheilmann Date: Fri, 28 Nov 2025 23:24:55 -0500 Subject: [PATCH 3/6] fix bug in grouping, and related tests --- lib/grpc_reflection/service/state.ex | 8 +- test/integration/v1_reflection_test.exs | 118 ++++--------------- test/integration/v1alpha_reflection_test.exs | 70 +++-------- test/service/builder_test.exs | 10 +- 4 files changed, 46 insertions(+), 160 deletions(-) diff --git a/lib/grpc_reflection/service/state.ex b/lib/grpc_reflection/service/state.ex index c8144a8..e27f16c 100644 --- a/lib/grpc_reflection/service/state.ex +++ b/lib/grpc_reflection/service/state.ex @@ -126,8 +126,8 @@ defmodule GrpcReflection.Service.State do # reference this new file # Step 1: Collect descriptors to be combined - symbol_files = Enum.map(symbols, &state.symbols[&1]) - files_to_combine = state.files |> Map.take(symbol_files) |> Map.values() + symbol_files = Enum.map(symbols, &state_acc.symbols[&1]) + files_to_combine = state_acc.files |> Map.take(symbol_files) |> Map.values() # Step 2: Combine the descriptors combined_file = @@ -162,11 +162,11 @@ defmodule GrpcReflection.Service.State do %{ state_acc | symbols: - state.symbols + state_acc.symbols |> Map.drop(symbols) |> Map.merge(Map.new(symbols, &{&1, cleaned_file.name})), files: - state.files + state_acc.files |> Map.drop(symbol_files) |> Map.new(fn {filename, descriptor} -> if Enum.any?(descriptor.dependency, &Enum.member?(symbol_files, &1)) do diff --git a/test/integration/v1_reflection_test.exs b/test/integration/v1_reflection_test.exs index ad04a1a..df2dd48 100644 --- a/test/integration/v1_reflection_test.exs +++ b/test/integration/v1_reflection_test.exs @@ -90,11 +90,8 @@ defmodule GrpcReflection.V1ReflectionTest do assert {:ok, response} = run_request(message, ctx) assert_response(response) - # we pretend all modules are in different files, dependencies are listed - assert response.dependency == [ - "helloworld.HelloRequest.proto", - "helloworld.HelloReply.proto" - ] + # we pretend each namespace is in a single file, dependencies are listed + assert response.dependency == ["google.protobuf.proto"] end test "reject filename that doesn't match a reflection module", ctx do @@ -104,39 +101,21 @@ defmodule GrpcReflection.V1ReflectionTest do end test "get replytype by filename", ctx do - filename = "helloworld.HelloReply.proto" + filename = "helloworld.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "helloworld" - assert response.dependency == ["google.protobuf.Timestamp.proto"] + assert response.dependency == ["google.protobuf.proto"] assert [ - %Google.Protobuf.DescriptorProto{ - name: "HelloReply", - field: [ - %Google.Protobuf.FieldDescriptorProto{ - name: "message", - number: 1, - label: :LABEL_OPTIONAL, - type: :TYPE_STRING, - json_name: "message" - }, - %Google.Protobuf.FieldDescriptorProto{ - name: "today", - number: 2, - label: :LABEL_OPTIONAL, - type: :TYPE_MESSAGE, - type_name: ".google.protobuf.Timestamp", - json_name: "today" - } - ] - } + %Google.Protobuf.DescriptorProto{name: "HelloReply"}, + %Google.Protobuf.DescriptorProto{name: "HelloRequest"} ] = response.message_type end test "get external by filename", ctx do - filename = "google.protobuf.Timestamp.proto" + filename = "google.protobuf.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -144,25 +123,9 @@ defmodule GrpcReflection.V1ReflectionTest do assert response.dependency == [] assert [ - %Google.Protobuf.DescriptorProto{ - field: [ - %Google.Protobuf.FieldDescriptorProto{ - json_name: "seconds", - label: :LABEL_OPTIONAL, - name: "seconds", - number: 1, - type: :TYPE_INT64 - }, - %Google.Protobuf.FieldDescriptorProto{ - json_name: "nanos", - label: :LABEL_OPTIONAL, - name: "nanos", - number: 2, - type: :TYPE_INT32 - } - ], - name: "Timestamp" - } + %Google.Protobuf.DescriptorProto{name: "Any"}, + %Google.Protobuf.DescriptorProto{name: "StringValue"}, + %Google.Protobuf.DescriptorProto{name: "Timestamp"} ] = response.message_type end @@ -173,11 +136,7 @@ defmodule GrpcReflection.V1ReflectionTest do assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == [ - "google.protobuf.Timestamp.proto", - "google.protobuf.StringValue.proto", - "google.protobuf.Any.proto" - ] + assert response.dependency == ["google.protobuf.proto"] end test "ensure exclusion of nested types in file descriptor dependencies", ctx do @@ -187,11 +146,7 @@ defmodule GrpcReflection.V1ReflectionTest do assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == [ - "google.protobuf.Timestamp.proto", - "google.protobuf.StringValue.proto", - "google.protobuf.Any.proto" - ] + assert response.dependency == ["google.protobuf.proto"] end end @@ -214,49 +169,18 @@ defmodule GrpcReflection.V1ReflectionTest do assert {:ok, response} = run_request(message, ctx) assert response.name == extendee <> "Extension.proto" assert response.package == "testserviceV2" - assert response.dependency == [extendee <> ".proto"] - - assert response.extension == [ - %Google.Protobuf.FieldDescriptorProto{ - name: "data", - extendee: extendee, - number: 10, - label: :LABEL_OPTIONAL, - type: :TYPE_STRING, - type_name: nil - }, - %Google.Protobuf.FieldDescriptorProto{ - name: "location", - extendee: extendee, - number: 11, - label: :LABEL_OPTIONAL, - type: :TYPE_MESSAGE, - type_name: "testserviceV2.Location" - } - ] + assert response.dependency == ["testserviceV2.proto"] + + assert [ + %Google.Protobuf.FieldDescriptorProto{name: "data"}, + %Google.Protobuf.FieldDescriptorProto{name: "location"} + ] = response.extension - assert response.message_type == [ + assert [ %Google.Protobuf.DescriptorProto{ - name: "Location", - field: [ - %Google.Protobuf.FieldDescriptorProto{ - name: "latitude", - number: 1, - label: :LABEL_OPTIONAL, - type: :TYPE_DOUBLE, - json_name: "latitude" - }, - %Google.Protobuf.FieldDescriptorProto{ - name: "longitude", - extendee: nil, - number: 2, - label: :LABEL_OPTIONAL, - type: :TYPE_DOUBLE, - json_name: "longitude" - } - ] + name: "Location" } - ] + ] = response.message_type end end end diff --git a/test/integration/v1alpha_reflection_test.exs b/test/integration/v1alpha_reflection_test.exs index 20253a2..761e1e2 100644 --- a/test/integration/v1alpha_reflection_test.exs +++ b/test/integration/v1alpha_reflection_test.exs @@ -91,10 +91,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert_response(response) # we pretend all modules are in different files, dependencies are listed - assert response.dependency == [ - "helloworld.HelloRequest.proto", - "helloworld.HelloReply.proto" - ] + assert response.dependency == ["google.protobuf.proto"] end test "reject filename that doesn't match a reflection module", ctx do @@ -104,39 +101,32 @@ defmodule GrpcReflection.V1alphaReflectionTest do end test "get replytype by filename", ctx do - filename = "helloworld.HelloReply.proto" + filename = "helloworld.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "helloworld" - assert response.dependency == ["google.protobuf.Timestamp.proto"] + assert response.dependency == ["google.protobuf.proto"] assert [ %Google.Protobuf.DescriptorProto{ name: "HelloReply", field: [ - %Google.Protobuf.FieldDescriptorProto{ - name: "message", - number: 1, - label: :LABEL_OPTIONAL, - type: :TYPE_STRING, - json_name: "message" - }, - %Google.Protobuf.FieldDescriptorProto{ - name: "today", - number: 2, - label: :LABEL_OPTIONAL, - type: :TYPE_MESSAGE, - type_name: ".google.protobuf.Timestamp", - json_name: "today" - } + %Google.Protobuf.FieldDescriptorProto{name: "message"}, + %Google.Protobuf.FieldDescriptorProto{name: "today"} + ] + }, + %Google.Protobuf.DescriptorProto{ + name: "HelloRequest", + field: [ + %Google.Protobuf.FieldDescriptorProto{name: "name"} ] } ] = response.message_type end test "get external by filename", ctx do - filename = "google.protobuf.Timestamp.proto" + filename = "google.protobuf.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -144,25 +134,9 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert response.dependency == [] assert [ - %Google.Protobuf.DescriptorProto{ - field: [ - %Google.Protobuf.FieldDescriptorProto{ - json_name: "seconds", - label: :LABEL_OPTIONAL, - name: "seconds", - number: 1, - type: :TYPE_INT64 - }, - %Google.Protobuf.FieldDescriptorProto{ - json_name: "nanos", - label: :LABEL_OPTIONAL, - name: "nanos", - number: 2, - type: :TYPE_INT32 - } - ], - name: "Timestamp" - } + %Google.Protobuf.DescriptorProto{name: "Any"}, + %Google.Protobuf.DescriptorProto{name: "StringValue"}, + %Google.Protobuf.DescriptorProto{name: "Timestamp"} ] = response.message_type end @@ -173,11 +147,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == [ - "google.protobuf.Timestamp.proto", - "google.protobuf.StringValue.proto", - "google.protobuf.Any.proto" - ] + assert response.dependency == ["google.protobuf.proto"] end test "ensure exclusion of nested types in file descriptor dependencies", ctx do @@ -187,11 +157,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == [ - "google.protobuf.Timestamp.proto", - "google.protobuf.StringValue.proto", - "google.protobuf.Any.proto" - ] + assert response.dependency == ["google.protobuf.proto"] end end @@ -217,7 +183,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert {:ok, response} = run_request(message, ctx) assert response.name == extendee <> "Extension.proto" assert response.package == "testserviceV2" - assert response.dependency == [extendee <> ".proto"] + assert response.dependency == ["testserviceV2.proto"] assert response.extension == [ %Google.Protobuf.FieldDescriptorProto{ diff --git a/test/service/builder_test.exs b/test/service/builder_test.exs index 2b605e1..af400a7 100644 --- a/test/service/builder_test.exs +++ b/test/service/builder_test.exs @@ -11,9 +11,7 @@ defmodule GrpcReflection.Service.BuilderTest do assert %State{services: [TestserviceV3.TestService.Service]} = tree assert Map.keys(tree.files) == [ - "google.protobuf.Any.proto", - "google.protobuf.StringValue.proto", - "google.protobuf.Timestamp.proto", + "google.protobuf.proto", "testserviceV3.proto" ] @@ -42,8 +40,7 @@ defmodule GrpcReflection.Service.BuilderTest do assert %State{services: [TestserviceV2.TestService.Service]} = tree assert Map.keys(tree.files) == [ - "google.protobuf.Any.proto", - "google.protobuf.Timestamp.proto", + "google.protobuf.proto", "testserviceV2.TestRequestExtension.proto", "testserviceV2.proto" ] @@ -101,8 +98,7 @@ defmodule GrpcReflection.Service.BuilderTest do names = Enum.map(Map.values(tree.files), & &1.name) assert names == [ - "google.protobuf.Any.proto", - "google.protobuf.Timestamp.proto", + "google.protobuf.proto", "testserviceV2.TestRequestExtension.proto", "testserviceV2.proto" ] From 197b00f33bbfcae168a899cd8e84643960edf19c Mon Sep 17 00:00:00 2001 From: mjheilmann Date: Sun, 7 Dec 2025 15:12:39 -0500 Subject: [PATCH 4/6] separate file comination logic from namespace grouping --- lib/grpc_reflection/service/state.ex | 108 +++++++++++++-------------- 1 file changed, 53 insertions(+), 55 deletions(-) diff --git a/lib/grpc_reflection/service/state.ex b/lib/grpc_reflection/service/state.ex index e27f16c..dcdb141 100644 --- a/lib/grpc_reflection/service/state.ex +++ b/lib/grpc_reflection/service/state.ex @@ -121,67 +121,65 @@ defmodule GrpcReflection.Service.State do |> Map.keys() |> Enum.group_by(&GrpcReflection.Service.Builder.Util.get_package(&1)) |> Enum.reduce(state, fn {package, symbols}, state_acc -> - # each symbol in symbols is in the same package - # we will combine their files into a single file, and update them to - # reference this new file - - # Step 1: Collect descriptors to be combined symbol_files = Enum.map(symbols, &state_acc.symbols[&1]) files_to_combine = state_acc.files |> Map.take(symbol_files) |> Map.values() + combined_file = combine_file_descriptors(package, files_to_combine) + update_state_with_combined_file(state_acc, combined_file, symbol_files) + end) + end - # Step 2: Combine the descriptors - combined_file = - Enum.reduce( - files_to_combine, - %Google.Protobuf.FileDescriptorProto{ - package: package, - name: package <> ".proto" - }, - fn descriptor, acc -> + defp update_state_with_combined_file(state, combined_file, combined_filenames) do + # Update files: remove old entries, add new one with updated dependencies + new_files = + state.files + |> Map.drop(combined_filenames) + |> Map.new(fn {filename, descriptor} -> + if Enum.any?(descriptor.dependency, &Enum.member?(combined_filenames, &1)) do + { + filename, %{ - acc - | syntax: descriptor.syntax, - message_type: Enum.uniq(acc.message_type ++ descriptor.message_type), - service: Enum.uniq(acc.service ++ descriptor.service), - enum_type: Enum.uniq(acc.enum_type ++ descriptor.enum_type), - dependency: Enum.uniq(acc.dependency ++ descriptor.dependency), - extension: Enum.uniq(acc.extension ++ descriptor.extension) + descriptor + | dependency: (descriptor.dependency -- combined_filenames) ++ [combined_file.name] } - end - ) - - # Step 3: remove internal dependency refs - cleaned_file = - %{combined_file | dependency: combined_file.dependency -- symbol_files} - - # Step 4: rework state around combined descriptor - # removing and re-adding symbols pointing to combined file - # removing combined file descriptors - # editing existing descriptors for relevant dependency entries - # add combined file descriptor + } + else + {filename, descriptor} + end + end) + |> Map.put(combined_file.name, combined_file) + + # Update symbols: map old symbols to point to the new combined file + new_symbols = + Map.new(state.symbols, fn {symbol, filename} -> + if filename in combined_filenames do + {symbol, combined_file.name} + else + {symbol, filename} + end + end) + + %{state | files: new_files, symbols: new_symbols} + end + + defp combine_file_descriptors(name, file_descriptors) do + acc = %Google.Protobuf.FileDescriptorProto{ + package: name, + name: name <> ".proto" + } + + combined_names = Enum.map(file_descriptors, & &1.name) + + Enum.reduce(file_descriptors, acc, fn descriptor, acc -> %{ - state_acc - | symbols: - state_acc.symbols - |> Map.drop(symbols) - |> Map.merge(Map.new(symbols, &{&1, cleaned_file.name})), - files: - state_acc.files - |> Map.drop(symbol_files) - |> Map.new(fn {filename, descriptor} -> - if Enum.any?(descriptor.dependency, &Enum.member?(symbol_files, &1)) do - { - filename, - %{ - descriptor - | dependency: (descriptor.dependency -- symbol_files) ++ [cleaned_file.name] - } - } - else - {filename, descriptor} - end - end) - |> Map.put(cleaned_file.name, cleaned_file) + acc + | syntax: acc.syntax || descriptor.syntax, + package: acc.package || descriptor.package, + name: acc.name || descriptor.name, + message_type: Enum.uniq(acc.message_type ++ descriptor.message_type), + service: Enum.uniq(acc.service ++ descriptor.service), + enum_type: Enum.uniq(acc.enum_type ++ descriptor.enum_type), + dependency: Enum.uniq(acc.dependency ++ (descriptor.dependency -- combined_names)), + extension: Enum.uniq(acc.extension ++ descriptor.extension) } end) end From a846967a8816dbd20ae6a8bdc86f594b644c20f9 Mon Sep 17 00:00:00 2001 From: mjheilmann Date: Mon, 8 Dec 2025 21:43:16 -0500 Subject: [PATCH 5/6] combine cycles instead of packages --- lib/grpc_reflection/service/builder.ex | 2 +- lib/grpc_reflection/service/cycle.ex | 65 ++++++++++ lib/grpc_reflection/service/state.ex | 27 ++--- test/integration/v1_reflection_test.exs | 38 +++--- test/integration/v1alpha_reflection_test.exs | 37 +++--- test/service/builder_test.exs | 27 ++++- test/service/cycle_test.exs | 118 +++++++++++++++++++ test/service/state_test.exs | 10 +- 8 files changed, 263 insertions(+), 61 deletions(-) create mode 100644 lib/grpc_reflection/service/cycle.ex create mode 100644 test/service/cycle_test.exs diff --git a/lib/grpc_reflection/service/builder.ex b/lib/grpc_reflection/service/builder.ex index 86e4189..69bcee2 100644 --- a/lib/grpc_reflection/service/builder.ex +++ b/lib/grpc_reflection/service/builder.ex @@ -14,7 +14,7 @@ defmodule GrpcReflection.Service.Builder do new_state = process_service(service) State.merge(state, new_state) end) - |> State.group_symbols_by_namespace() + |> State.shrink_cycles() {:ok, tree} end diff --git a/lib/grpc_reflection/service/cycle.ex b/lib/grpc_reflection/service/cycle.ex new file mode 100644 index 0000000..a8a56ff --- /dev/null +++ b/lib/grpc_reflection/service/cycle.ex @@ -0,0 +1,65 @@ +defmodule GrpcReflection.Service.Cycle do + @moduledoc """ + Find and identify cycles in a state graph + """ + + defmodule DFS do + @moduledoc false + defstruct visited: [], path: [], cycles: [] + end + + def get_cycles(%GrpcReflection.Service.State{files: files}) do + files + |> Map.values() + |> Enum.reject(fn file -> + String.ends_with?(file.name, "Extension.proto") + end) + |> Map.new(fn file -> {file.name, file.dependency} end) + |> find_cycles() + end + + def find_cycles(graph) do + graph + |> Map.keys() + |> Enum.reduce(%DFS{}, fn node, acc -> + %{ + dfs(node, graph, acc) + | path: acc.path + } + end) + |> Map.fetch!(:cycles) + |> Enum.map(&Enum.sort/1) + |> Enum.sort() + |> Enum.uniq() + end + + defp dfs(node, graph, state) do + cond do + Enum.member?(state.path, node) -> + # Node is in current path → cycle found + cycle = [node | Enum.take_while(state.path, &(&1 != node))] + %{state | cycles: [cycle | state.cycles]} + + Enum.member?(state.visited, node) -> + state + + true -> + # Mark as visited and extend path + state = %{ + state + | visited: [node | state.visited], + path: [node | state.path] + } + + # Visit neighbors + graph + |> Map.get(node, []) + |> Enum.reduce(state, fn neighbor, acc -> + %{ + dfs(neighbor, graph, acc) + | path: acc.path + } + end) + end + end +end diff --git a/lib/grpc_reflection/service/state.ex b/lib/grpc_reflection/service/state.ex index dcdb141..16e4a76 100644 --- a/lib/grpc_reflection/service/state.ex +++ b/lib/grpc_reflection/service/state.ex @@ -116,15 +116,15 @@ defmodule GrpcReflection.Service.State do end end - def group_symbols_by_namespace(%__MODULE__{} = state) do - state.symbols - |> Map.keys() - |> Enum.group_by(&GrpcReflection.Service.Builder.Util.get_package(&1)) - |> Enum.reduce(state, fn {package, symbols}, state_acc -> - symbol_files = Enum.map(symbols, &state_acc.symbols[&1]) - files_to_combine = state_acc.files |> Map.take(symbol_files) |> Map.values() - combined_file = combine_file_descriptors(package, files_to_combine) - update_state_with_combined_file(state_acc, combined_file, symbol_files) + # reduce state size and complexity by combining files into fewer, larger responses + def shrink_cycles(%__MODULE__{} = state) do + state + |> GrpcReflection.Service.Cycle.get_cycles() + |> Enum.reduce(state, fn + filenames, state_acc -> + files = Enum.map(filenames, &state.files[&1]) + combined_file = combine_file_descriptors(files) + update_state_with_combined_file(state_acc, combined_file, filenames) end) end @@ -161,15 +161,10 @@ defmodule GrpcReflection.Service.State do %{state | files: new_files, symbols: new_symbols} end - defp combine_file_descriptors(name, file_descriptors) do - acc = %Google.Protobuf.FileDescriptorProto{ - package: name, - name: name <> ".proto" - } - + defp combine_file_descriptors(file_descriptors) do combined_names = Enum.map(file_descriptors, & &1.name) - Enum.reduce(file_descriptors, acc, fn descriptor, acc -> + Enum.reduce(file_descriptors, %Google.Protobuf.FileDescriptorProto{}, fn descriptor, acc -> %{ acc | syntax: acc.syntax || descriptor.syntax, diff --git a/test/integration/v1_reflection_test.exs b/test/integration/v1_reflection_test.exs index df2dd48..68b4972 100644 --- a/test/integration/v1_reflection_test.exs +++ b/test/integration/v1_reflection_test.exs @@ -74,7 +74,7 @@ defmodule GrpcReflection.V1ReflectionTest do test "describing a nested type returns the root type", ctx do message = {:file_containing_symbol, "testserviceV3.TestRequest.Payload"} assert {:ok, response} = run_request(message, ctx) - assert response.name == "testserviceV3.proto" + assert response.name == "testserviceV3.TestRequest.proto" end test "type with leading period still resolves", ctx do @@ -91,7 +91,10 @@ defmodule GrpcReflection.V1ReflectionTest do assert_response(response) # we pretend each namespace is in a single file, dependencies are listed - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == [ + "helloworld.HelloRequest.proto", + "helloworld.HelloReply.proto" + ] end test "reject filename that doesn't match a reflection module", ctx do @@ -101,21 +104,20 @@ defmodule GrpcReflection.V1ReflectionTest do end test "get replytype by filename", ctx do - filename = "helloworld.proto" + filename = "helloworld.HelloReply.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "helloworld" - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == ["google.protobuf.Timestamp.proto"] assert [ - %Google.Protobuf.DescriptorProto{name: "HelloReply"}, - %Google.Protobuf.DescriptorProto{name: "HelloRequest"} + %Google.Protobuf.DescriptorProto{name: "HelloReply"} ] = response.message_type end test "get external by filename", ctx do - filename = "google.protobuf.proto" + filename = "google.protobuf.Any.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -123,30 +125,36 @@ defmodule GrpcReflection.V1ReflectionTest do assert response.dependency == [] assert [ - %Google.Protobuf.DescriptorProto{name: "Any"}, - %Google.Protobuf.DescriptorProto{name: "StringValue"}, - %Google.Protobuf.DescriptorProto{name: "Timestamp"} + %Google.Protobuf.DescriptorProto{name: "Any"} ] = response.message_type end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.proto" + filename = "testserviceV3.TestRequest.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == [ + "testserviceV3.Enum.proto", + "google.protobuf.Any.proto", + "google.protobuf.StringValue.proto" + ] end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.proto" + filename = "testserviceV3.TestRequest.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == [ + "testserviceV3.Enum.proto", + "google.protobuf.Any.proto", + "google.protobuf.StringValue.proto" + ] end end @@ -169,7 +177,7 @@ defmodule GrpcReflection.V1ReflectionTest do assert {:ok, response} = run_request(message, ctx) assert response.name == extendee <> "Extension.proto" assert response.package == "testserviceV2" - assert response.dependency == ["testserviceV2.proto"] + assert response.dependency == ["testserviceV2.TestRequest.proto"] assert [ %Google.Protobuf.FieldDescriptorProto{name: "data"}, diff --git a/test/integration/v1alpha_reflection_test.exs b/test/integration/v1alpha_reflection_test.exs index 761e1e2..99579e5 100644 --- a/test/integration/v1alpha_reflection_test.exs +++ b/test/integration/v1alpha_reflection_test.exs @@ -74,7 +74,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do test "describing a nested type returns the root type", ctx do message = {:file_containing_symbol, "testserviceV3.TestRequest.Payload"} assert {:ok, response} = run_request(message, ctx) - assert response.name == "testserviceV3.proto" + assert response.name == "testserviceV3.TestRequest.proto" end test "type with leading period still resolves", ctx do @@ -91,7 +91,10 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert_response(response) # we pretend all modules are in different files, dependencies are listed - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == [ + "helloworld.HelloRequest.proto", + "helloworld.HelloReply.proto" + ] end test "reject filename that doesn't match a reflection module", ctx do @@ -101,12 +104,12 @@ defmodule GrpcReflection.V1alphaReflectionTest do end test "get replytype by filename", ctx do - filename = "helloworld.proto" + filename = "helloworld.HelloReply.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "helloworld" - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == ["google.protobuf.Timestamp.proto"] assert [ %Google.Protobuf.DescriptorProto{ @@ -115,18 +118,12 @@ defmodule GrpcReflection.V1alphaReflectionTest do %Google.Protobuf.FieldDescriptorProto{name: "message"}, %Google.Protobuf.FieldDescriptorProto{name: "today"} ] - }, - %Google.Protobuf.DescriptorProto{ - name: "HelloRequest", - field: [ - %Google.Protobuf.FieldDescriptorProto{name: "name"} - ] } ] = response.message_type end test "get external by filename", ctx do - filename = "google.protobuf.proto" + filename = "google.protobuf.Timestamp.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -134,30 +131,34 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert response.dependency == [] assert [ - %Google.Protobuf.DescriptorProto{name: "Any"}, - %Google.Protobuf.DescriptorProto{name: "StringValue"}, %Google.Protobuf.DescriptorProto{name: "Timestamp"} ] = response.message_type end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.proto" + filename = "testserviceV3.TestReply.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == [ + "google.protobuf.Timestamp.proto", + "google.protobuf.StringValue.proto" + ] end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.proto" + filename = "testserviceV3.TestReply.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" - assert response.dependency == ["google.protobuf.proto"] + assert response.dependency == [ + "google.protobuf.Timestamp.proto", + "google.protobuf.StringValue.proto" + ] end end @@ -183,7 +184,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do assert {:ok, response} = run_request(message, ctx) assert response.name == extendee <> "Extension.proto" assert response.package == "testserviceV2" - assert response.dependency == ["testserviceV2.proto"] + assert response.dependency == ["testserviceV2.TestRequest.proto"] assert response.extension == [ %Google.Protobuf.FieldDescriptorProto{ diff --git a/test/service/builder_test.exs b/test/service/builder_test.exs index af400a7..40e8129 100644 --- a/test/service/builder_test.exs +++ b/test/service/builder_test.exs @@ -11,8 +11,15 @@ defmodule GrpcReflection.Service.BuilderTest do assert %State{services: [TestserviceV3.TestService.Service]} = tree assert Map.keys(tree.files) == [ - "google.protobuf.proto", - "testserviceV3.proto" + "google.protobuf.Any.proto", + "google.protobuf.StringValue.proto", + "google.protobuf.Timestamp.proto", + "testserviceV3.Enum.proto", + "testserviceV3.TestReply.proto", + "testserviceV3.TestRequest.Payload.proto", + "testserviceV3.TestRequest.Token.proto", + "testserviceV3.TestRequest.proto", + "testserviceV3.TestService.proto" ] assert Map.keys(tree.symbols) == [ @@ -40,9 +47,13 @@ defmodule GrpcReflection.Service.BuilderTest do assert %State{services: [TestserviceV2.TestService.Service]} = tree assert Map.keys(tree.files) == [ - "google.protobuf.proto", + "google.protobuf.Any.proto", + "google.protobuf.Timestamp.proto", + "testserviceV2.Enum.proto", + "testserviceV2.TestReply.proto", + "testserviceV2.TestRequest.proto", "testserviceV2.TestRequestExtension.proto", - "testserviceV2.proto" + "testserviceV2.TestService.proto" ] assert Map.keys(tree.symbols) == [ @@ -98,9 +109,13 @@ defmodule GrpcReflection.Service.BuilderTest do names = Enum.map(Map.values(tree.files), & &1.name) assert names == [ - "google.protobuf.proto", + "google.protobuf.Any.proto", + "google.protobuf.Timestamp.proto", + "testserviceV2.Enum.proto", + "testserviceV2.TestReply.proto", + "testserviceV2.TestRequest.proto", "testserviceV2.TestRequestExtension.proto", - "testserviceV2.proto" + "testserviceV2.TestService.proto" ] end diff --git a/test/service/cycle_test.exs b/test/service/cycle_test.exs new file mode 100644 index 0000000..a0b8d2a --- /dev/null +++ b/test/service/cycle_test.exs @@ -0,0 +1,118 @@ +defmodule GrpcReflection.Service.CycleTest do + @moduledoc false + + use ExUnit.Case + + alias GrpcReflection.Service.Cycle + + describe "get_cycles" do + test "should return empty list for empty graph" do + assert Cycle.find_cycles(%{}) == [] + end + + test "should return empty list when no cycles exist" do + graph = %{ + "A" => ["B"], + "B" => ["C"], + "C" => ["D"] + } + + assert Cycle.find_cycles(graph) == [] + end + + test "should detect a cycle in a simple cycle A → B → A" do + graph = %{ + "A" => ["B"], + "B" => ["A"] + } + + assert Cycle.find_cycles(graph) == [ + ["A", "B"] + ] + end + + test "should detect two distinct cycles" do + graph = %{ + "A" => ["B"], + "B" => ["A"], + "C" => ["D"], + "D" => ["C"] + } + + assert Cycle.find_cycles(graph) == [ + ["A", "B"], + ["C", "D"] + ] + end + + test "should detect one cycle in a complex graph" do + graph = %{ + "A" => ["B", "C"], + "B" => ["C"], + "C" => ["D"], + "D" => ["A"] + } + + assert Cycle.find_cycles(graph) == [ + ["A", "B", "C", "D"] + ] + end + + test "should detect cycle with indirect path" do + graph = %{ + "A" => ["B"], + "B" => ["C"], + "C" => ["D"], + "D" => ["A"] + } + + assert Cycle.find_cycles(graph) == [ + ["A", "B", "C", "D"] + ] + end + + test "should return empty list for a single node with no edges" do + graph = %{"A" => []} + assert Cycle.find_cycles(graph) == [] + end + + test "should ignore nodes with no outgoing edges" do + graph = %{ + "A" => [], + "B" => ["C"], + "C" => ["B"] + } + + assert Cycle.find_cycles(graph) == [ + ["B", "C"] + ] + end + + test "should not duplicate one cycle when referenced multiple times" do + graph = %{ + "A" => ["C"], + "B" => ["C"], + "C" => ["D"], + "D" => ["C"] + } + + assert Cycle.find_cycles(graph) == [ + ["C", "D"] + ] + end + + test "should handle nodes with many relations" do + graph = %{ + "A" => ["B", "C", "D", "E"], + "B" => [], + "C" => [], + "D" => [], + "E" => ["A"] + } + + assert Cycle.find_cycles(graph) == [ + ["A", "E"] + ] + end + end +end diff --git a/test/service/state_test.exs b/test/service/state_test.exs index 0336f32..b36e102 100644 --- a/test/service/state_test.exs +++ b/test/service/state_test.exs @@ -56,7 +56,7 @@ defmodule GrpcReflection.Service.StateTest do end end - describe "group_symbols_by_namespace" do + describe "shrink_cycles" do setup do state_with_recursion = %State{ services: ["Service1", "Service2"], @@ -93,7 +93,7 @@ defmodule GrpcReflection.Service.StateTest do } %{ - state: State.group_symbols_by_namespace(state_with_recursion) + state: State.shrink_cycles(state_with_recursion) } end @@ -105,14 +105,14 @@ defmodule GrpcReflection.Service.StateTest do assert [combined_file, other_file] = Map.values(state.files) # combined file is present as we expect assert combined_file.dependency == [] - assert combined_file.name == "common.path.proto" + assert combined_file.name == "file1.proto" # referencing file is updated as we expect - assert other_file.dependency == ["common.path.proto"] + assert other_file.dependency == ["file1.proto"] assert other_file.name == "file3.proto" end test "should combine descriptors", %{state: state} do - file = state.files["common.path.proto"] + file = state.files["file1.proto"] symbols = Enum.map(file.message_type, & &1.name) assert symbols == ["Symbol_a", "Symbol_b"] assert file.service == ["A", "B"] From 5ae81e89afaa3752908280bed05f191dd720e0b5 Mon Sep 17 00:00:00 2001 From: mjheilmann Date: Mon, 8 Dec 2025 21:46:31 -0500 Subject: [PATCH 6/6] reduce test changes --- test/integration/v1_reflection_test.exs | 11 +++++------ test/integration/v1alpha_reflection_test.exs | 5 +++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/integration/v1_reflection_test.exs b/test/integration/v1_reflection_test.exs index 68b4972..ce2b914 100644 --- a/test/integration/v1_reflection_test.exs +++ b/test/integration/v1_reflection_test.exs @@ -90,7 +90,7 @@ defmodule GrpcReflection.V1ReflectionTest do assert {:ok, response} = run_request(message, ctx) assert_response(response) - # we pretend each namespace is in a single file, dependencies are listed + # we pretend all modules are in different files, dependencies are listed assert response.dependency == [ "helloworld.HelloRequest.proto", "helloworld.HelloReply.proto" @@ -117,7 +117,7 @@ defmodule GrpcReflection.V1ReflectionTest do end test "get external by filename", ctx do - filename = "google.protobuf.Any.proto" + filename = "google.protobuf.Timestamp.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -125,20 +125,19 @@ defmodule GrpcReflection.V1ReflectionTest do assert response.dependency == [] assert [ - %Google.Protobuf.DescriptorProto{name: "Any"} + %Google.Protobuf.DescriptorProto{name: "Timestamp"} ] = response.message_type end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.TestRequest.proto" + filename = "testserviceV3.TestReply.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" assert response.dependency == [ - "testserviceV3.Enum.proto", - "google.protobuf.Any.proto", + "google.protobuf.Timestamp.proto", "google.protobuf.StringValue.proto" ] end diff --git a/test/integration/v1alpha_reflection_test.exs b/test/integration/v1alpha_reflection_test.exs index 99579e5..3623000 100644 --- a/test/integration/v1alpha_reflection_test.exs +++ b/test/integration/v1alpha_reflection_test.exs @@ -149,14 +149,15 @@ defmodule GrpcReflection.V1alphaReflectionTest do end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.TestReply.proto" + filename = "testserviceV3.TestRequest.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename assert response.package == "testserviceV3" assert response.dependency == [ - "google.protobuf.Timestamp.proto", + "testserviceV3.Enum.proto", + "google.protobuf.Any.proto", "google.protobuf.StringValue.proto" ] end