-module(grpcbox_reflection_service). -export([server_reflection_info/2]). -include("grpcbox.hrl"). -define(UNIMPLEMENTED_RESPONSE, {error_response, #{error_code => 12, error_message => "unimplemented method since extensions removed in proto3"}}). server_reflection_info(Ref, Stream) -> receive {Ref, eos} -> ok; {Ref, Message} -> handle_message(Message, Stream), server_reflection_info(Ref, Stream) end. handle_message(#{message_request := {list_services, _}}=OriginalRequest, Stream) -> Services = list_services(), grpcbox_stream:send(#{original_request => OriginalRequest, message_response => {list_services_response, #{service => Services}}}, Stream); handle_message(#{message_request := {file_by_filename, Filename}}=OriginalRequest, Stream) -> Response = file_by_filename(Filename), grpcbox_stream:send(#{original_request => OriginalRequest, message_response => Response}, Stream); handle_message(#{message_request := {file_containing_symbol, Symbol}}=OriginalRequest, Stream) -> Response = file_containing_symbol(Symbol), grpcbox_stream:send(#{original_request => OriginalRequest, message_response => Response}, Stream); %% proto3 dropped extensions so we'll just return an empty result handle_message(#{message_request := {all_extension_numbers_of_type, _}}=OriginalRequest, Stream) -> grpcbox_stream:send(#{original_request => OriginalRequest, message_response => ?UNIMPLEMENTED_RESPONSE}, Stream); handle_message(#{message_request := {file_containing_extension, _}}=OriginalRequest, Stream) -> grpcbox_stream:send(#{original_request => OriginalRequest, message_response => ?UNIMPLEMENTED_RESPONSE}, Stream). %% list_services() -> ServiceSups = supervisor:which_children(grpcbox_services_simple_sup), lists:usort(lists:flatmap(fun services/1, ServiceSups)). %% Find the name of the services for a service supervisor services({_, Pid, _, _}) -> {registered_name, Name} = erlang:process_info(Pid, registered_name), [#{name => Service} || Service <- ets:select(Name, [{#method{key={'$1', '_'}, _='_'}, [], ['$1']}])]. file_by_filename(Filename) -> FilenameList = binary_to_list(Filename), {file_descriptor_response, #{file_descriptor_proto => [grpcbox_health_pb:descriptor(FilenameList)]}}. file_containing_symbol(Symbol) -> ServiceSups = supervisor:which_children(grpcbox_services_simple_sup), ServicePbModules = lists:usort(lists:flatmap(fun proto_modules/1, ServiceSups)), find(ServicePbModules, Symbol). %% looks up all protobuf modules generated by gpb related to a service supervisor proto_modules({_, Pid, _, _}) -> {registered_name, Name} = erlang:process_info(Pid, registered_name), [Module || Module <- ets:select(Name, [{#method{proto='$1', _='_'}, [], ['$1']}])]. %% check all the fqbin lookup functions for the provided symbol find([], _) -> {error_response, #{error_code => 5, error_message => "symbol not found"}}; find([M | T], Symbol) -> case find_all(fun(F) -> find_symbol(M, F, Symbol) end, [get_proto_by_msg_name_as_fqbin, get_proto_by_service_name_as_fqbin, get_proto_by_enum_name_as_fqbin, get_protos_by_pkg_name_as_fqbin]) of [P | _] -> {file_descriptor_response, #{file_descriptor_proto => [M:descriptor(P)]}}; [] -> find(T, Symbol) end. find_symbol(M, F, Symbol) -> try M:F(Symbol) of Proto -> Proto catch _:_ -> false end. find_all(F, L) -> find_all(F, L, []). find_all(_, [], Acc) -> Acc; find_all(F, [H | T], Acc) -> case F(H) of false -> find_all(F, T, Acc); Proto -> find_all(F, T, [Proto | Acc]) end.