diff --git a/.gitignore b/.gitignore index d331e4d..a7188de 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,8 @@ grpc_reflection-*.tar # Ignore dialyzer PLT files priv/plts + +# Ignore cursor and memory-bank +.cursor/ +memory-bank/ +custom_modes/ diff --git a/lib/grpc_reflection/service/builder.ex b/lib/grpc_reflection/service/builder.ex index 7425970..05f88c3 100644 --- a/lib/grpc_reflection/service/builder.ex +++ b/lib/grpc_reflection/service/builder.ex @@ -2,149 +2,243 @@ defmodule GrpcReflection.Service.Builder do @moduledoc false alias Google.Protobuf.FileDescriptorProto - alias GrpcReflection.Service.State - alias GrpcReflection.Service.Builder.Util + alias GrpcReflection.Service.Builder.Acc alias GrpcReflection.Service.Builder.Extensions + alias GrpcReflection.Service.Builder.Util + alias GrpcReflection.Service.State + @spec build_reflection_tree(any()) :: + {:error, <<_::216>>} | {:ok, GrpcReflection.Service.State.t()} def build_reflection_tree(services) do with :ok <- Util.validate_services(services) do - tree = - Enum.reduce(services, State.new(services), fn service, state -> - new_state = process_service(service) - State.merge(state, new_state) + acc = + Enum.reduce(services, %Acc{services: services}, fn service, acc -> + process_service(acc, service) end) - {:ok, tree} + {:ok, finalize(acc)} end end - defp process_service(service) do - service_name = service.__meta__(:name) - service_response = build_response(service_name, service) + defp finalize(%Acc{} = acc) do + files = build_files(acc) + + symbols = + acc.symbol_info + |> Enum.reduce(%{}, fn {symbol, info}, map -> + Map.put(map, symbol, Map.fetch!(files, info.file)) + end) + |> then(fn base -> + Enum.reduce(acc.aliases, base, fn {alias_symbol, target_symbol}, map -> + payload = Map.fetch!(map, target_symbol) + Map.put(map, alias_symbol, payload) + end) + end) + + State.new(acc.services) + |> State.add_files(Map.merge(files, acc.extension_files)) + |> State.add_symbols(symbols) + |> State.add_extensions(acc.extensions) + end + + defp build_files(%Acc{} = acc) do + acc.symbol_info + |> Enum.group_by(fn {_symbol, info} -> info.file end) + |> Enum.reduce(%{}, fn {file_name, entries}, files -> + descriptors = Enum.map(entries, fn {_symbol, info} -> info end) + + syntax = descriptors |> List.first() |> Map.fetch!(:syntax) + package = descriptors |> List.first() |> Map.fetch!(:package) + + dependencies = + descriptors + |> Enum.flat_map(& &1.deps) + |> Enum.map(&resolve_dependency_file(&1, acc)) + |> Enum.reject(&is_nil/1) + |> Enum.reject(&(&1 == file_name)) + |> Enum.uniq() + + {messages, enums, services} = + Enum.reduce(descriptors, {[], [], []}, fn %{descriptor: descriptor}, + {messages, enums, services} -> + case descriptor do + %Google.Protobuf.DescriptorProto{} -> + {[descriptor | messages], enums, services} + + %Google.Protobuf.EnumDescriptorProto{} -> + {messages, [descriptor | enums], services} + + %Google.Protobuf.ServiceDescriptorProto{} -> + {messages, enums, [descriptor | services]} + end + end) + |> then(fn {messages, enums, services} -> + {Enum.reverse(messages), Enum.reverse(enums), Enum.reverse(services)} + end) + + file_proto = %FileDescriptorProto{ + name: file_name, + package: package, + dependency: dependencies, + syntax: syntax, + message_type: messages, + enum_type: enums, + service: services + } - State.new() - |> State.add_symbols(%{service_name => service_response}) - |> State.add_files(%{(service_name <> ".proto") => service_response}) - |> trace_service_refs(service) + Map.put(files, file_name, %{file_descriptor_proto: [FileDescriptorProto.encode(file_proto)]}) + end) end - defp trace_service_refs(state, module) do - service_name = module.__meta__(:name) - methods = get_descriptor(module).method + defp process_service(%Acc{} = acc, service) do + service_name = service.__meta__(:name) + {acc, _} = register_symbol(acc, service_name, service, :service) + + methods = get_descriptor(service).method - module.__rpc_calls__() - |> Enum.reduce(state, fn call, state -> - {function, {request, _}, {response, _}, _} = call + Enum.reduce(service.__rpc_calls__(), acc, fn call, acc -> + {function, {req, _}, {resp, _}, _} = call %{input_type: req_symbol, output_type: resp_symbol} = Enum.find(methods, fn method -> method.name == to_string(function) end) - call_symbol = service_name <> "." <> to_string(function) - call_response = build_response(service_name, module) req_symbol = Util.trim_symbol(req_symbol) - req_response = build_response(req_symbol, request) resp_symbol = Util.trim_symbol(resp_symbol) - resp_response = build_response(resp_symbol, response) - - state - |> Extensions.add_extensions(service_name, module) - |> State.add_symbols(%{ - call_symbol => call_response, - req_symbol => req_response, - resp_symbol => resp_response - }) - |> State.add_files(%{ - (req_symbol <> ".proto") => req_response, - (resp_symbol <> ".proto") => resp_response - }) - |> Extensions.add_extensions(req_symbol, request) - |> Extensions.add_extensions(resp_symbol, response) - |> trace_message_refs(req_symbol, request) - |> trace_message_refs(resp_symbol, response) + + method_symbol = service_name <> "." <> to_string(function) + + acc + |> register_alias(method_symbol, service_name) + |> Extensions.add_extensions(service_name, service) + |> process_message(req_symbol, req) + |> process_message(resp_symbol, resp) end) end - defp trace_message_refs(state, parent_symbol, module) do - case module.descriptor() do - %{field: fields} -> - trace_message_fields(state, parent_symbol, module, fields) + defp process_message(%Acc{} = acc, nil, _module, _root_symbol), do: acc - _ -> - state - end - end + defp process_message(%Acc{} = acc, symbol, module, root_symbol) do + symbol = Util.trim_symbol(symbol) + root_symbol = root_symbol || symbol - defp trace_message_fields(state, parent_symbol, module, fields) do - # nested types arent a "separate file", they return their parents' response - nested_types = Util.get_nested_types(parent_symbol, module.descriptor()) - - module.__message_props__().field_props - |> Map.values() - |> Enum.map(fn %{name: name, type: type} -> - %{ - mod: - case type do - {_, mod} -> mod - mod -> mod - end, - symbol: Enum.find(fields, fn f -> f.name == name end).type_name - } - end) - |> Enum.reject(fn %{symbol: s} -> s == nil end) - |> Enum.reduce(state, fn %{mod: mod, symbol: symbol}, state -> - symbol = Util.trim_symbol(symbol) + {acc, already_processed} = + if root_symbol == symbol do + register_symbol(acc, symbol, module, :message) + else + acc = register_alias(acc, symbol, root_symbol) - response = - if symbol in nested_types do - build_response(parent_symbol, module) + if MapSet.member?(acc.visited, symbol) do + {acc, true} else - build_response(symbol, mod) + {acc, false} end + end - state - |> Extensions.add_extensions(symbol, mod) - |> State.add_symbols(%{symbol => response}) - |> State.add_files(%{(symbol <> ".proto") => response}) - |> trace_message_refs(symbol, mod) - end) + if already_processed do + acc + else + acc = %{acc | visited: MapSet.put(acc.visited, symbol)} + acc = Extensions.add_extensions(acc, symbol, module) + + case module.descriptor() do + %{field: fields} = descriptor -> + nested_symbols = Util.get_nested_types(symbol, descriptor) + + module.__message_props__().field_props + |> Map.values() + |> Enum.map(fn %{name: name, type: type} -> + %{ + mod: + case type do + {_, mod} -> mod + mod -> mod + end, + symbol: Enum.find(fields, fn f -> f.name == name end).type_name + } + end) + |> Enum.reject(fn %{symbol: s} -> is_nil(s) end) + |> Enum.reduce(acc, fn %{mod: mod, symbol: child_symbol}, acc -> + child_symbol = Util.trim_symbol(child_symbol) + + if child_symbol in nested_symbols do + process_message(acc, child_symbol, mod, root_symbol) + else + process_message(acc, child_symbol, mod) + end + end) + + _ -> + acc + end + end end - defp build_response(symbol, module) do - # we build our own file responses, so unwrap any present - descriptor = get_descriptor(module) - - dependencies = - descriptor - |> Util.types_from_descriptor() - |> Enum.uniq() - |> Kernel.--(Util.get_nested_types(symbol, descriptor)) - |> Enum.map(fn name -> - Util.trim_symbol(name) <> ".proto" - end) - - syntax = Util.get_syntax(module) + defp process_message(%Acc{} = acc, symbol, module) do + process_message(acc, symbol, module, nil) + end - response_stub = - %FileDescriptorProto{ - name: symbol <> ".proto", + defp register_symbol(%Acc{} = acc, symbol, module, kind) do + symbol = Util.trim_symbol(symbol) + + if Map.has_key?(acc.symbol_info, symbol) do + {acc, true} + else + descriptor = get_descriptor(module) + + info = %{ + descriptor: descriptor, + deps: + descriptor + |> Util.types_from_descriptor() + |> Enum.map(&Util.trim_symbol/1) + |> Enum.uniq(), + file: Util.proto_filename(module), + syntax: Util.get_syntax(module), package: Util.get_package(symbol), - dependency: dependencies, - syntax: syntax + kind: kind } - unencoded_payload = - case descriptor = descriptor do - %Google.Protobuf.DescriptorProto{} -> %{response_stub | message_type: [descriptor]} - %Google.Protobuf.ServiceDescriptorProto{} -> %{response_stub | service: [descriptor]} - %Google.Protobuf.EnumDescriptorProto{} -> %{response_stub | enum_type: [descriptor]} - end + {%{ + acc + | symbol_info: Map.put(acc.symbol_info, symbol, info), + visited: MapSet.put(acc.visited, symbol) + }, false} + end + end + + defp register_alias(%Acc{} = acc, alias_symbol, target_symbol) do + alias_symbol = Util.trim_symbol(alias_symbol) + target_symbol = Util.trim_symbol(target_symbol) + + cond do + alias_symbol == target_symbol -> acc + Map.get(acc.aliases, alias_symbol) == target_symbol -> acc + true -> %{acc | aliases: Map.put(acc.aliases, alias_symbol, target_symbol)} + end + end - %{file_descriptor_proto: [FileDescriptorProto.encode(unencoded_payload)]} + defp resolve_dependency_file(nil, _acc), do: nil + + defp resolve_dependency_file(symbol, %Acc{} = acc) do + symbol = Util.trim_symbol(symbol) + + cond do + info = Map.get(acc.symbol_info, symbol) -> + info.file + + target = Map.get(acc.aliases, symbol) -> + acc.symbol_info + |> Map.get(target) + |> case do + nil -> symbol <> ".proto" + info -> info.file + end + + true -> + symbol <> ".proto" + end end - # protoc with the elixir generator and protobuf.generate slightly differ for how they - # generate descriptors. Use this to potentially unwrap the service proto when dealing - # with descriptors that could come from a service module. defp get_descriptor(module) do case module.descriptor() do %FileDescriptorProto{service: [proto]} -> proto diff --git a/lib/grpc_reflection/service/builder/acc.ex b/lib/grpc_reflection/service/builder/acc.ex new file mode 100644 index 0000000..10d539c --- /dev/null +++ b/lib/grpc_reflection/service/builder/acc.ex @@ -0,0 +1,10 @@ +defmodule GrpcReflection.Service.Builder.Acc do + @moduledoc false + + defstruct services: [], + symbol_info: %{}, + aliases: %{}, + visited: MapSet.new(), + extension_files: %{}, + extensions: %{} +end diff --git a/lib/grpc_reflection/service/builder/extensions.ex b/lib/grpc_reflection/service/builder/extensions.ex index 1e0bcf9..9c25b87 100644 --- a/lib/grpc_reflection/service/builder/extensions.ex +++ b/lib/grpc_reflection/service/builder/extensions.ex @@ -2,20 +2,27 @@ defmodule GrpcReflection.Service.Builder.Extensions do @moduledoc false alias Google.Protobuf.FileDescriptorProto + alias GrpcReflection.Service.Builder.Acc alias GrpcReflection.Service.Builder.Util - alias GrpcReflection.Service.State - def add_extensions(state, symbol, module) do + def add_extensions(%Acc{} = acc, symbol, module) do extension_file = symbol <> "Extension.proto" case process_extensions(module, symbol, extension_file, module.descriptor()) do {:ok, {extension_numbers, extension_payload}} -> - state - |> State.add_files(%{extension_file => %{file_descriptor_proto: [extension_payload]}}) - |> State.add_extensions(%{symbol => extension_numbers}) + extension_map = %{extension_file => %{file_descriptor_proto: [extension_payload]}} + + %{ + acc + | extension_files: Map.merge(acc.extension_files, extension_map), + extensions: + Map.update(acc.extensions, symbol, extension_numbers, fn existing -> + Enum.uniq(existing ++ extension_numbers) + end) + } :ignore -> - state + acc end end @@ -44,7 +51,7 @@ defmodule GrpcReflection.Service.Builder.Extensions do unencoded_extension_payload = %Google.Protobuf.FileDescriptorProto{ name: extension_file, package: Util.get_package(symbol), - dependency: [symbol <> ".proto"], + dependency: [Util.proto_filename(mod)], syntax: Util.get_syntax(mod), extension: Enum.map(extensions, fn {_, _, ext} -> ext end), message_type: message_list diff --git a/lib/grpc_reflection/service/builder/util.ex b/lib/grpc_reflection/service/builder/util.ex index b4a50c9..49bcd37 100644 --- a/lib/grpc_reflection/service/builder/util.ex +++ b/lib/grpc_reflection/service/builder/util.ex @@ -159,4 +159,54 @@ defmodule GrpcReflection.Service.Builder.Util do def trim_symbol("." <> symbol), do: symbol def trim_symbol(symbol), do: symbol + + def proto_filename(module) do + module.__info__(:compile) + |> Keyword.get(:source) + |> case do + nil -> + module + |> Atom.to_string() + |> String.trim_leading("Elixir.") + |> then(&(&1 <> ".proto")) + + source -> + source + |> List.to_string() + |> String.trim() + |> build_proto_name() + end + end + + defp build_proto_name(path) do + base = + cond do + String.ends_with?(path, ".pb.ex") -> String.replace_suffix(path, ".pb.ex", "") + String.ends_with?(path, ".ex") -> String.replace_suffix(path, ".ex", "") + true -> path + end + + cond do + String.contains?(base, "/priv/protos/") -> + [_prefix, rest] = String.split(base, "/priv/protos/", parts: 2) + normalize_proto_path(rest) <> ".proto" + + String.contains?(base, "/test/support/protos/") -> + [_prefix, rest] = String.split(base, "/test/support/protos/", parts: 2) + Path.basename(rest) <> ".proto" + + String.contains?(base, "/lib/") -> + [_prefix, rest] = String.split(base, "/lib/", parts: 2) + normalize_proto_path(rest) <> ".proto" + + true -> + Path.basename(base) <> ".proto" + end + end + + defp normalize_proto_path(path) do + path + |> String.split(["/", "\\"], trim: true) + |> Enum.join("/") + end end diff --git a/test/service/builder_test.exs b/test/service/builder_test.exs index da12e2d..f77c45a 100644 --- a/test/service/builder_test.exs +++ b/test/service/builder_test.exs @@ -10,34 +10,29 @@ defmodule GrpcReflection.Service.BuilderTest do assert {:ok, tree} = Builder.build_reflection_tree([TestserviceV3.TestService.Service]) assert %State{services: [TestserviceV3.TestService.Service]} = tree - assert Map.keys(tree.files) == [ - "google.protobuf.Any.proto", - "google.protobuf.StringValue.proto", - "google.protobuf.Timestamp.proto", - "testserviceV3.Enum.proto", - "testserviceV3.TestReply.proto", - "testserviceV3.TestRequest.GEntry.proto", - "testserviceV3.TestRequest.Payload.Location.proto", - "testserviceV3.TestRequest.Payload.proto", - "testserviceV3.TestRequest.Token.proto", - "testserviceV3.TestRequest.proto", - "testserviceV3.TestService.proto" - ] - - assert Map.keys(tree.symbols) == [ - "google.protobuf.Any", - "google.protobuf.StringValue", - "google.protobuf.Timestamp", - "testserviceV3.Enum", - "testserviceV3.TestReply", - "testserviceV3.TestRequest", - "testserviceV3.TestRequest.GEntry", - "testserviceV3.TestRequest.Payload", - "testserviceV3.TestRequest.Payload.Location", - "testserviceV3.TestRequest.Token", - "testserviceV3.TestService", - "testserviceV3.TestService.CallFunction" - ] + assert tree.files |> Map.keys() |> Enum.sort() == + Enum.sort([ + "google/protobuf/any.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/wrappers.proto", + "test_service_v3.proto" + ]) + + assert tree.symbols |> Map.keys() |> Enum.sort() == + Enum.sort([ + "google.protobuf.Any", + "google.protobuf.StringValue", + "google.protobuf.Timestamp", + "testserviceV3.Enum", + "testserviceV3.TestReply", + "testserviceV3.TestRequest", + "testserviceV3.TestRequest.GEntry", + "testserviceV3.TestRequest.Payload", + "testserviceV3.TestRequest.Payload.Location", + "testserviceV3.TestRequest.Token", + "testserviceV3.TestService", + "testserviceV3.TestService.CallFunction" + ]) (Map.values(tree.files) ++ Map.values(tree.symbols)) |> Enum.flat_map(&Map.get(&1, :file_descriptor_proto)) @@ -51,27 +46,25 @@ defmodule GrpcReflection.Service.BuilderTest do assert {:ok, tree} = Builder.build_reflection_tree([TestserviceV2.TestService.Service]) assert %State{services: [TestserviceV2.TestService.Service]} = tree - assert Map.keys(tree.files) == [ - "google.protobuf.Any.proto", - "google.protobuf.Timestamp.proto", - "testserviceV2.Enum.proto", - "testserviceV2.TestReply.proto", - "testserviceV2.TestRequest.GEntry.proto", - "testserviceV2.TestRequest.proto", - "testserviceV2.TestRequestExtension.proto", - "testserviceV2.TestService.proto" - ] - - assert Map.keys(tree.symbols) == [ - "google.protobuf.Any", - "google.protobuf.Timestamp", - "testserviceV2.Enum", - "testserviceV2.TestReply", - "testserviceV2.TestRequest", - "testserviceV2.TestRequest.GEntry", - "testserviceV2.TestService", - "testserviceV2.TestService.CallFunction" - ] + assert tree.files |> Map.keys() |> Enum.sort() == + Enum.sort([ + "google/protobuf/any.proto", + "google/protobuf/timestamp.proto", + "test_service_v2.proto", + "testserviceV2.TestRequestExtension.proto" + ]) + + assert tree.symbols |> Map.keys() |> Enum.sort() == + Enum.sort([ + "google.protobuf.Any", + "google.protobuf.Timestamp", + "testserviceV2.Enum", + "testserviceV2.TestReply", + "testserviceV2.TestRequest", + "testserviceV2.TestRequest.GEntry", + "testserviceV2.TestService", + "testserviceV2.TestService.CallFunction" + ]) assert %{ "testserviceV2.TestRequest" => extensions @@ -125,25 +118,13 @@ defmodule GrpcReflection.Service.BuilderTest do |> Enum.map(&Google.Protobuf.FileDescriptorProto.decode/1) |> Enum.map(& &1.name) - assert names == - [ - "google.protobuf.Any.proto", - "google.protobuf.Timestamp.proto", - "testserviceV2.Enum.proto", - "testserviceV2.TestReply.proto", - "testserviceV2.TestRequest.proto", - "testserviceV2.TestRequest.proto", - "testserviceV2.TestRequestExtension.proto", - "testserviceV2.TestService.proto", - "google.protobuf.Any.proto", - "google.protobuf.Timestamp.proto", - "testserviceV2.Enum.proto", - "testserviceV2.TestReply.proto", - "testserviceV2.TestRequest.proto", - "testserviceV2.TestRequest.proto", - "testserviceV2.TestService.proto", - "testserviceV2.TestService.proto" - ] + assert Enum.sort(Enum.uniq(names)) == + Enum.sort([ + "custom_prefix_service.proto", + "google/protobuf/any.proto", + "google/protobuf/timestamp.proto", + "testserviceV2.TestRequestExtension.proto" + ]) end test "handles a non-service module" do diff --git a/test/service/server_test.exs b/test/service/server_test.exs index 39d29c1..c2fcb1b 100644 --- a/test/service/server_test.exs +++ b/test/service/server_test.exs @@ -13,7 +13,7 @@ defmodule GrpcReflection.ServerTest do assert Service.get_by_symbol("helloworld.Greeter") == {:error, "symbol not found"} - assert Service.get_by_filename("helloworld.Greeter.proto") == + assert Service.get_by_filename("helloworld.proto") == {:error, "filename not found"} assert :ok == Service.put_services([Helloworld.Greeter.Service]) @@ -24,7 +24,7 @@ defmodule GrpcReflection.ServerTest do Service.get_by_symbol("helloworld.Greeter") assert {:ok, %{file_descriptor_proto: ^proto}} = - Service.get_by_filename("helloworld.Greeter.proto") + Service.get_by_filename("helloworld.proto") end describe "reflection state testing" do @@ -57,7 +57,7 @@ defmodule GrpcReflection.ServerTest do Service.get_by_symbol("helloworld.HelloRequest") assert {:ok, %{file_descriptor_proto: ^proto}} = - Service.get_by_filename("helloworld.HelloRequest.proto") + Service.get_by_filename("helloworld.proto") end test "type with leading period still resolves" do @@ -65,7 +65,7 @@ defmodule GrpcReflection.ServerTest do Service.get_by_symbol(".helloworld.HelloRequest") assert {:ok, %{file_descriptor_proto: ^proto}} = - Service.get_by_filename("helloworld.HelloRequest.proto") + Service.get_by_filename("helloworld.proto") end end end diff --git a/test/v1_reflection_test.exs b/test/v1_reflection_test.exs index c951993..534b1a0 100644 --- a/test/v1_reflection_test.exs +++ b/test/v1_reflection_test.exs @@ -2,6 +2,7 @@ defmodule GrpcReflection.V1ReflectionTest do @moduledoc false use GrpcCase + alias GrpcReflection.Service.Builder.Util @moduletag capture_log: true @@ -74,7 +75,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 == "test_service_v3.proto" end test "type with leading period still resolves", ctx do @@ -90,11 +91,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" - ] + assert [%{name: "Greeter", method: methods}] = response.service + assert Enum.map(methods, & &1.name) == ["SayHello"] end test "reject filename that doesn't match a reflection module", ctx do @@ -104,12 +102,12 @@ 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/timestamp.proto"] assert [ %Google.Protobuf.DescriptorProto{ @@ -136,7 +134,7 @@ defmodule GrpcReflection.V1ReflectionTest do end test "get external by filename", ctx do - filename = "google.protobuf.Timestamp.proto" + filename = "google/protobuf/timestamp.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -167,30 +165,33 @@ defmodule GrpcReflection.V1ReflectionTest do end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.TestReply.proto" + filename = "test_service_v3.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", - "google.protobuf.StringValue.proto" - ] + assert Enum.sort(response.dependency) == + Enum.sort([ + "google/protobuf/timestamp.proto", + "google/protobuf/wrappers.proto", + "google/protobuf/any.proto" + ]) end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.TestRequest.proto" + filename = "test_service_v3.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" - ] + assert Enum.sort(response.dependency) == + Enum.sort([ + "google/protobuf/any.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/wrappers.proto" + ]) end end @@ -213,7 +214,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 == [extendee <> ".proto"] + assert response.dependency == [Util.proto_filename(TestserviceV2.TestRequest)] assert response.extension == [ %Google.Protobuf.FieldDescriptorProto{ diff --git a/test/v1alpha_reflection_test.exs b/test/v1alpha_reflection_test.exs index 4236a63..c0166aa 100644 --- a/test/v1alpha_reflection_test.exs +++ b/test/v1alpha_reflection_test.exs @@ -2,6 +2,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do @moduledoc false use GrpcCase + alias GrpcReflection.Service.Builder.Util @moduletag capture_log: true @@ -74,7 +75,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 == "test_service_v3.proto" end test "type with leading period still resolves", ctx do @@ -90,11 +91,8 @@ defmodule GrpcReflection.V1alphaReflectionTest 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" - ] + assert [%{name: "Greeter", method: methods}] = response.service + assert Enum.map(methods, & &1.name) == ["SayHello"] end test "reject filename that doesn't match a reflection module", ctx do @@ -104,12 +102,12 @@ 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/timestamp.proto"] assert [ %Google.Protobuf.DescriptorProto{ @@ -136,7 +134,7 @@ defmodule GrpcReflection.V1alphaReflectionTest do end test "get external by filename", ctx do - filename = "google.protobuf.Timestamp.proto" + filename = "google/protobuf/timestamp.proto" message = {:file_by_filename, filename} assert {:ok, response} = run_request(message, ctx) assert response.name == filename @@ -167,30 +165,33 @@ defmodule GrpcReflection.V1alphaReflectionTest do end test "ensures file descriptor dependencies are unique", ctx do - filename = "testserviceV3.TestReply.proto" + filename = "test_service_v3.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", - "google.protobuf.StringValue.proto" - ] + assert Enum.sort(response.dependency) == + Enum.sort([ + "google/protobuf/timestamp.proto", + "google/protobuf/wrappers.proto", + "google/protobuf/any.proto" + ]) end test "ensure exclusion of nested types in file descriptor dependencies", ctx do - filename = "testserviceV3.TestRequest.proto" + filename = "test_service_v3.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" - ] + assert Enum.sort(response.dependency) == + Enum.sort([ + "google/protobuf/any.proto", + "google/protobuf/timestamp.proto", + "google/protobuf/wrappers.proto" + ]) end end @@ -216,7 +217,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 == [Util.proto_filename(TestserviceV2.TestRequest)] assert response.extension == [ %Google.Protobuf.FieldDescriptorProto{