From c971ad3ca5380327b33c63e65e95c871dfbdb245 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Sun, 9 Nov 2025 22:13:09 +0900 Subject: [PATCH 01/10] Generate file descriptors map for gRPC reflection API --- examples/echo_reflection/dune | 32 +++++++++++++++++ examples/echo_reflection/echo.proto | 25 ++++++++++++++ examples/echo_reflection/test.ml | 6 ++++ src/plugin/emit.ml | 53 +++++++++++++++++++++++++---- src/plugin/emit.mli | 9 ++++- src/plugin/parameters.ml | 9 +++-- src/plugin/protoc_gen_ocaml.ml | 39 +++++++++++++++------ 7 files changed, 152 insertions(+), 21 deletions(-) create mode 100644 examples/echo_reflection/dune create mode 100644 examples/echo_reflection/echo.proto create mode 100644 examples/echo_reflection/test.ml diff --git a/examples/echo_reflection/dune b/examples/echo_reflection/dune new file mode 100644 index 0000000..6be0d0e --- /dev/null +++ b/examples/echo_reflection/dune @@ -0,0 +1,32 @@ +(executable + (name test) + (libraries ocaml-protoc-plugin ocaml-protoc-plugin.google_types unix)) + +(rule + (targets google_include) + (action + (with-stdout-to + %{targets} + (system "pkg-config protobuf --variable=includedir")))) + +(rule + (targets echo.ml reflection_map.ml) + (deps + (:proto echo.proto) + (package ocaml-protoc-plugin)) + (action + (run + protoc + -I + %{read-lines:google_include} + -I + . + "--ocaml_out=open=Google_types;reflection_map:." + %{proto}))) + +(rule + (deps test.exe) + (action + (ignore-stdout + (run %{deps}))) + (alias runtest)) diff --git a/examples/echo_reflection/echo.proto b/examples/echo_reflection/echo.proto new file mode 100644 index 0000000..0c12545 --- /dev/null +++ b/examples/echo_reflection/echo.proto @@ -0,0 +1,25 @@ +syntax = "proto3"; +import "google/protobuf/timestamp.proto"; + +package echo; +message Request { + enum who { + Mum = 0; + World = 1; + } + oneof ts { + google.protobuf.Timestamp timestamp = 1; + }; + oneof what { + who type = 10; + string someone = 11; + } +} + +message Reply { + string response = 1; +} + +service Echo { + rpc Call (Request) returns (Reply); +} diff --git a/examples/echo_reflection/test.ml b/examples/echo_reflection/test.ml new file mode 100644 index 0000000..4f4fea2 --- /dev/null +++ b/examples/echo_reflection/test.ml @@ -0,0 +1,6 @@ +open Echo + +let () = + assert (List.mem Echo.Echo.package_service_name Reflection_map.services); + assert (Reflection_map.get_file_descriptor "echo.proto" |> Option.is_some); + assert (Reflection_map.get_service_descriptor "echo.Echo" |> Option.is_some); diff --git a/src/plugin/emit.ml b/src/plugin/emit.ml index db656bb..b024799 100644 --- a/src/plugin/emit.ml +++ b/src/plugin/emit.ml @@ -93,7 +93,7 @@ let emit_enum_type ~scope ~params ~type_db ~comment_db let comments = Comment_db.get_enum_comments comment_db ~proto_path ~name:enum_name in { module_name; signature; implementation; deprecated; comments } -let emit_service_type ~scope ~comment_db ~type_db ServiceDescriptorProto.{ name; method' = methods; options = service_options; _ } = +let emit_service_type ~scope ~comment_db ~type_db ~refl_service_db ~file_index ServiceDescriptorProto.{ name; method' = methods; options = service_options; _ } = let proto_path = Scope.get_proto_path scope in let emit_method ~type_db ~scope signature implementation service_name MethodDescriptorProto.{ name; input_type; output_type; options = method_options; _} = @@ -153,6 +153,7 @@ let emit_service_type ~scope ~comment_db ~type_db ServiceDescriptorProto.{ name; | "" :: packages | packages -> (String.concat ~sep:"." packages) ^ "." ^ name in + Hashtbl.add refl_service_db ~key:package_service_name ~data:file_index; let signature = Code.init () in let implementation = Code.init () in Code.emit_comment ~position:`Leading signature (Comment_db.get_service_comments comment_db ~proto_path ~name); @@ -388,12 +389,12 @@ let rec emit_message ~params ~syntax ~scope ~type_db ~comment_db let comments = Comment_db.get_message_comments comment_db ~proto_path:(Scope.get_proto_path scope) in { module_name; signature; implementation; deprecated; comments } -let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~scope message_type services = function +let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~refl_service_db ~file_index ~scope message_type services = function | [] -> let { module_name = _; implementation; signature; deprecated = _; comments = _ } = emit_message ~params ~syntax ~scope ~comment_db ~type_db message_type in List.iter ~f:(fun service -> - let signature', implementation' = emit_service_type ~scope ~type_db ~comment_db service in + let signature', implementation' = emit_service_type ~scope ~type_db ~comment_db ~refl_service_db ~file_index service in Code.append implementation implementation'; Code.append signature signature'; () @@ -408,7 +409,7 @@ let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~scope messa let scope = Scope.push scope package in let signature', implementation' = - wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db message_type services packages + wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db ~refl_service_db ~file_index message_type services packages in Code.emit implementation `Begin "module rec %s : sig" package_name; Code.append implementation signature'; @@ -446,7 +447,7 @@ let emit_header implementation ~proto_name ~syntax ~params = (* Code.emit implementation `None "%s" (Code.append_deprecaton_if ~deprecated `Floating ""); *) () -let parse_proto_file ~params ~scope ~type_db filedescriptorproto = +let parse_proto_file ~params ~scope ~type_db ~refl_service_db (filedescriptorproto, file_index) = let FileDescriptorProto.{ name = proto_name; package; dependency = dependencies; public_dependency = _; weak_dependency = _; message_type = message_types; @@ -484,7 +485,7 @@ let parse_proto_file ~params ~scope ~type_db filedescriptorproto = Code.emit implementation `None "(**/**)"; let _signature', implementation' = - wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db message_type services (Option.value_map ~default:[] ~f:(String.split_on_char ~sep:'.') package) + wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db ~refl_service_db ~file_index message_type services (Option.value_map ~default:[] ~f:(String.split_on_char ~sep:'.') package) in Code.append implementation implementation'; @@ -496,3 +497,43 @@ let parse_proto_file ~params ~scope ~type_db filedescriptorproto = |> Printf.sprintf "%s.ml" in output_file_name, implementation + +let reflection ~file_descriptors ~refl_file_db ~refl_service_db = + let implementation = Code.init () in + + Code.emit implementation `None "(********************************************************)"; + Code.emit implementation `None "(* AUTOGENERATED FILE - DO NOT EDIT! *)"; + Code.emit implementation `None "(********************************************************)"; + Code.emit implementation `None "(* Generated by: ocaml-protoc-plugin *)"; + Code.emit implementation `None "(* https://github.com/andersfugmann/ocaml-protoc-plugin *)"; + Code.emit implementation `None "(********************************************************)"; + Code.emit implementation `None "[@@@ocaml.alert \"-protobuf\"] (* Disable deprecation warnings for protobuf*)"; + Code.emit implementation `None ""; + + Code.emit implementation `Begin "let file_descriptors = [|"; + Dynarray.iter (fun fd_bytes -> + Code.emit implementation `None {|"%s";|} fd_bytes + ) file_descriptors; + Code.emit implementation `None "|]"; + Code.emit implementation `End ""; + + Code.emit implementation `Begin "let get_file_descriptor name ="; + Code.emit implementation `None "match name with"; + Hashtbl.iter refl_file_db ~f:(fun ~key ~data -> + Code.emit implementation `None {s|| "%s" -> Some (file_descriptors.(%d)) |s} key data); + Code.emit implementation `None "| _ -> None"; + Code.emit implementation `End ""; + + Code.emit implementation `Begin "let get_service_descriptor name ="; + Code.emit implementation `None "match name with"; + Hashtbl.iter refl_service_db ~f:(fun ~key ~data -> + Code.emit implementation `None {s|| "%s" -> Some (file_descriptors.(%d)) |s} key data); + Code.emit implementation `None "| _ -> None"; + Code.emit implementation `End ""; + + Code.emit implementation `Begin "let services = ["; + Hashtbl.iter refl_service_db ~f:(fun ~key ~data:_ -> + Code.emit implementation `None {|"%s";|} key); + Code.emit implementation `End "]"; + + implementation diff --git a/src/plugin/emit.mli b/src/plugin/emit.mli index aae3873..44c9c96 100644 --- a/src/plugin/emit.mli +++ b/src/plugin/emit.mli @@ -1,4 +1,11 @@ open Spec.Descriptor.Google.Protobuf val parse_proto_file: params:Parameters.t -> - scope:Scope.t -> type_db:Type_db.t -> FileDescriptorProto.t -> string * Code.t + scope:Scope.t -> type_db:Type_db.t -> + refl_service_db:(string, int) MoreLabels.Hashtbl.t -> + (FileDescriptorProto.t * int) -> string * Code.t + +val reflection : + file_descriptors:string Dynarray.t -> + refl_file_db:(string, int) MoreLabels.Hashtbl.t -> + refl_service_db:(string, int) MoreLabels.Hashtbl.t -> Code.t diff --git a/src/plugin/parameters.ml b/src/plugin/parameters.ml index f724482..a19b63b 100644 --- a/src/plugin/parameters.ml +++ b/src/plugin/parameters.ml @@ -10,6 +10,7 @@ type t = { singleton_record: bool; prefix_output_with_package: bool; singleton_oneof_as_option: bool; + reflection_map: bool; } let default = { @@ -22,6 +23,7 @@ let default = { singleton_record = false; prefix_output_with_package = false; singleton_oneof_as_option = true; + reflection_map = false; } let parse_option str = @@ -41,9 +43,10 @@ let parse parameters = | `Expr ("int64_as_int", (("true"|"false") as v)) -> { param with int64_as_int = (bool_of_string v) }; | `Expr ("int32_as_int", (("true"|"false") as v)) -> { param with int32_as_int = (bool_of_string v) }; | `Expr ("singleton_record", (("true"|"false") as v)) -> { param with singleton_record = (bool_of_string v) }; - | `Stmt "debug" -> { param with debug = true} - | `Expr ("prefix_output_with_package", (("true"|"false") as v)) -> { param with prefix_output_with_package = (bool_of_string v)} - | `Expr ("singleton_oneof_as_option", (("true"|"false") as v)) -> { param with singleton_oneof_as_option = (bool_of_string v)} + | `Stmt "debug" -> { param with debug = true } + | `Expr ("prefix_output_with_package", (("true"|"false") as v)) -> { param with prefix_output_with_package = (bool_of_string v) } + | `Expr ("singleton_oneof_as_option", (("true"|"false") as v)) -> { param with singleton_oneof_as_option = (bool_of_string v) } + | `Stmt "reflection_map" -> { param with reflection_map = true } | `Stmt "" -> param | _ -> failwith ("Unknown parameter: " ^ option) ) diff --git a/src/plugin/protoc_gen_ocaml.ml b/src/plugin/protoc_gen_ocaml.ml index 0165c2d..619be5e 100644 --- a/src/plugin/protoc_gen_ocaml.ml +++ b/src/plugin/protoc_gen_ocaml.ml @@ -27,40 +27,57 @@ let write response = |> Ocaml_protoc_plugin.Writer.contents |> output_string stdout - -let parse_request Plugin.CodeGeneratorRequest.{file_to_generate = files_to_generate; parameter = parameters; proto_file = proto_files; compiler_version = _} = - let params = Parameters.parse (Option.value ~default:"" parameters) in +let parse_request params files_to_generate proto_files = let target_proto_files = List.filter ~f:(fun Descriptor.FileDescriptorProto.{name; _} -> List.mem ~set:files_to_generate (Option.value_exn name) ) proto_files in let type_db = Type_db.init ~params proto_files in + let fds = Dynarray.create () in + let refl_service_db = Hashtbl .create 16 in + let refl_file_db = Hashtbl.create 16 in let result = - List.map ~f:(fun (proto_file : Descriptor.FileDescriptorProto.t) -> + List.mapi ~f:(fun idx (proto_file : Descriptor.FileDescriptorProto.t) -> + let file_descriptor_bytes = let v = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto proto_file in + Ocaml_protoc_plugin.Writer.contents v |> String.escaped + in let proto_file_name = Option.value_exn ~message:"All files must have a name" proto_file.name in + Hashtbl.add refl_file_db proto_file_name idx; + Dynarray.add_last fds file_descriptor_bytes; let scope = Scope.init ~module_name:(Type_db.get_module_name type_db proto_file_name) in - Emit.parse_proto_file ~params ~scope ~type_db proto_file + Emit.parse_proto_file ~params ~scope ~type_db ~refl_service_db (proto_file, idx) ) target_proto_files - |> List.map ~f:(fun (name, code) -> - (name, code) - ) in + let result_reflection = Emit.reflection ~file_descriptors:fds ~refl_file_db ~refl_service_db in (match params.debug with | true -> List.iter ~f:(fun (_, code) -> Printf.eprintf "%s\n%!" (Code.contents code)) result | false -> ()); - result + result, result_reflection let () = let request = read () in try - let outputs = parse_request request in + let Plugin.CodeGeneratorRequest.{parameter = parameters; file_to_generate = files_to_generate; proto_file = proto_files; _} = request in + let params = + Parameters.parse (Option.value ~default:"" parameters) + in + let (outputs, reflection) = parse_request params files_to_generate proto_files in let response_of_output (name, code) = Plugin.CodeGeneratorResponse.File.make ~name ~content:(Code.contents code) () in let response : Plugin.CodeGeneratorResponse.t = Plugin.CodeGeneratorResponse.make ~supported_features:1 ~file:(List.map ~f:response_of_output outputs) () in - write response + write response; + if params.reflection_map then + let response_of_reflection = Plugin.CodeGeneratorResponse.make ~file:[Plugin.CodeGeneratorResponse.File. + make + ~name:"reflection_map.ml" + ~content:(Code.contents reflection) + (); + ] () in + write response_of_reflection + with | Failure message -> (Printf.eprintf "%s\n" message; exit 1) From 5f1a4c23e4583f265836c6a0a95cd6f4234fa157 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Mon, 10 Nov 2025 18:05:16 +0900 Subject: [PATCH 02/10] fixup: include metainfo instead generating files --- examples/echo_reflection/dune | 32 ---------- examples/echo_reflection/echo.proto | 25 -------- examples/echo_reflection/test.ml | 6 -- src/plugin/emit.ml | 93 +++++++++++------------------ src/plugin/emit.mli | 9 +-- src/plugin/parameters.ml | 9 +-- src/plugin/protoc_gen_ocaml.ml | 39 ++++-------- 7 files changed, 50 insertions(+), 163 deletions(-) delete mode 100644 examples/echo_reflection/dune delete mode 100644 examples/echo_reflection/echo.proto delete mode 100644 examples/echo_reflection/test.ml diff --git a/examples/echo_reflection/dune b/examples/echo_reflection/dune deleted file mode 100644 index 6be0d0e..0000000 --- a/examples/echo_reflection/dune +++ /dev/null @@ -1,32 +0,0 @@ -(executable - (name test) - (libraries ocaml-protoc-plugin ocaml-protoc-plugin.google_types unix)) - -(rule - (targets google_include) - (action - (with-stdout-to - %{targets} - (system "pkg-config protobuf --variable=includedir")))) - -(rule - (targets echo.ml reflection_map.ml) - (deps - (:proto echo.proto) - (package ocaml-protoc-plugin)) - (action - (run - protoc - -I - %{read-lines:google_include} - -I - . - "--ocaml_out=open=Google_types;reflection_map:." - %{proto}))) - -(rule - (deps test.exe) - (action - (ignore-stdout - (run %{deps}))) - (alias runtest)) diff --git a/examples/echo_reflection/echo.proto b/examples/echo_reflection/echo.proto deleted file mode 100644 index 0c12545..0000000 --- a/examples/echo_reflection/echo.proto +++ /dev/null @@ -1,25 +0,0 @@ -syntax = "proto3"; -import "google/protobuf/timestamp.proto"; - -package echo; -message Request { - enum who { - Mum = 0; - World = 1; - } - oneof ts { - google.protobuf.Timestamp timestamp = 1; - }; - oneof what { - who type = 10; - string someone = 11; - } -} - -message Reply { - string response = 1; -} - -service Echo { - rpc Call (Request) returns (Reply); -} diff --git a/examples/echo_reflection/test.ml b/examples/echo_reflection/test.ml deleted file mode 100644 index 4f4fea2..0000000 --- a/examples/echo_reflection/test.ml +++ /dev/null @@ -1,6 +0,0 @@ -open Echo - -let () = - assert (List.mem Echo.Echo.package_service_name Reflection_map.services); - assert (Reflection_map.get_file_descriptor "echo.proto" |> Option.is_some); - assert (Reflection_map.get_service_descriptor "echo.Echo" |> Option.is_some); diff --git a/src/plugin/emit.ml b/src/plugin/emit.ml index b024799..3361d42 100644 --- a/src/plugin/emit.ml +++ b/src/plugin/emit.ml @@ -93,7 +93,7 @@ let emit_enum_type ~scope ~params ~type_db ~comment_db let comments = Comment_db.get_enum_comments comment_db ~proto_path ~name:enum_name in { module_name; signature; implementation; deprecated; comments } -let emit_service_type ~scope ~comment_db ~type_db ~refl_service_db ~file_index ServiceDescriptorProto.{ name; method' = methods; options = service_options; _ } = +let emit_service_type ~scope ~comment_db ~type_db ServiceDescriptorProto.{ name; method' = methods; options = service_options; _ } = let proto_path = Scope.get_proto_path scope in let emit_method ~type_db ~scope signature implementation service_name MethodDescriptorProto.{ name; input_type; output_type; options = method_options; _} = @@ -153,7 +153,7 @@ let emit_service_type ~scope ~comment_db ~type_db ~refl_service_db ~file_index S | "" :: packages | packages -> (String.concat ~sep:"." packages) ^ "." ^ name in - Hashtbl.add refl_service_db ~key:package_service_name ~data:file_index; + let signature = Code.init () in let implementation = Code.init () in Code.emit_comment ~position:`Leading signature (Comment_db.get_service_comments comment_db ~proto_path ~name); @@ -167,7 +167,7 @@ let emit_service_type ~scope ~comment_db ~type_db ~refl_service_db ~file_index S Code.emit signature `None ""; Code.emit implementation `End "end%s" (Code.append_deprecaton_if ~deprecated `Item ""); Code.emit implementation `None ""; - signature, implementation + signature, implementation, package_service_name let emit_extension ~scope ~params ~comment_db ~type_db field = let FieldDescriptorProto.{ name; extendee; options; _ } = field in @@ -389,18 +389,19 @@ let rec emit_message ~params ~syntax ~scope ~type_db ~comment_db let comments = Comment_db.get_message_comments comment_db ~proto_path:(Scope.get_proto_path scope) in { module_name; signature; implementation; deprecated; comments } -let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~refl_service_db ~file_index ~scope message_type services = function +let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~scope message_type services = function | [] -> let { module_name = _; implementation; signature; deprecated = _; comments = _ } = emit_message ~params ~syntax ~scope ~comment_db ~type_db message_type in - List.iter ~f:(fun service -> - let signature', implementation' = emit_service_type ~scope ~type_db ~comment_db ~refl_service_db ~file_index service in - Code.append implementation implementation'; - Code.append signature signature'; - () - ) services; - signature, implementation - + let package_service_names = + List.map ~f:(fun service -> + let signature', implementation', package_service_name = emit_service_type ~scope ~type_db ~comment_db service in + Code.append implementation implementation'; + Code.append signature signature'; + package_service_name + ) services + in + signature, implementation, package_service_names | package :: packages -> let signature = Code.init () in let implementation = Code.init () in @@ -408,8 +409,8 @@ let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~refl_servic let package_name = Type_db.get_package_name type_db ~proto_path package in let scope = Scope.push scope package in - let signature', implementation' = - wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db ~refl_service_db ~file_index message_type services packages + let signature', implementation', package_service_names = + wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db message_type services packages in Code.emit implementation `Begin "module rec %s : sig" package_name; Code.append implementation signature'; @@ -419,7 +420,7 @@ let rec wrap_packages ~params ~syntax ~options ~comment_db ~type_db ~refl_servic Code.emit signature `Begin "module rec %s : sig" package_name; Code.append signature signature'; Code.emit signature `End "end"; - signature, implementation + signature, implementation, package_service_names let emit_header implementation ~proto_name ~syntax ~params = @@ -447,7 +448,21 @@ let emit_header implementation ~proto_name ~syntax ~params = (* Code.emit implementation `None "%s" (Code.append_deprecaton_if ~deprecated `Floating ""); *) () -let parse_proto_file ~params ~scope ~type_db ~refl_service_db (filedescriptorproto, file_index) = +let emit_metainfo implementation fd package_service_names = + let file_descriptor_bytes = let v = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto fd in + Ocaml_protoc_plugin.Writer.contents v |> String.escaped + in + Code.emit implementation `Begin "module Metainfo : sig"; + Code.emit implementation `None "val file_descriptor_proto : string"; + Code.emit implementation `None "val package_service_names : string list"; + Code.emit implementation `EndBegin "end = struct"; + Code.emit implementation `None {|let file_descriptor_proto = "%s"|} file_descriptor_bytes; + Code.emit implementation `None "let package_service_names = ["; + List.iter ~f:(fun name -> Code.emit implementation `None "\"%s\";" name) package_service_names; + Code.emit implementation `None "]"; + Code.emit implementation `End "end" + +let parse_proto_file ~params ~scope ~type_db filedescriptorproto = let FileDescriptorProto.{ name = proto_name; package; dependency = dependencies; public_dependency = _; weak_dependency = _; message_type = message_types; @@ -484,56 +499,18 @@ let parse_proto_file ~params ~scope ~type_db ~refl_service_db (filedescriptorpro Code.emit implementation `End "end"; Code.emit implementation `None "(**/**)"; - let _signature', implementation' = - wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db ~refl_service_db ~file_index message_type services (Option.value_map ~default:[] ~f:(String.split_on_char ~sep:'.') package) + let _signature', implementation', package_service_names = + wrap_packages ~params ~syntax ~options ~scope ~type_db ~comment_db message_type services (Option.value_map ~default:[] ~f:(String.split_on_char ~sep:'.') package) in Code.append implementation implementation'; Code.emit implementation `None ""; + emit_metainfo implementation filedescriptorproto package_service_names; + let output_file_name = Type_db.get_module_name type_db proto_name |> String.uncapitalize_ascii |> Printf.sprintf "%s.ml" in output_file_name, implementation - -let reflection ~file_descriptors ~refl_file_db ~refl_service_db = - let implementation = Code.init () in - - Code.emit implementation `None "(********************************************************)"; - Code.emit implementation `None "(* AUTOGENERATED FILE - DO NOT EDIT! *)"; - Code.emit implementation `None "(********************************************************)"; - Code.emit implementation `None "(* Generated by: ocaml-protoc-plugin *)"; - Code.emit implementation `None "(* https://github.com/andersfugmann/ocaml-protoc-plugin *)"; - Code.emit implementation `None "(********************************************************)"; - Code.emit implementation `None "[@@@ocaml.alert \"-protobuf\"] (* Disable deprecation warnings for protobuf*)"; - Code.emit implementation `None ""; - - Code.emit implementation `Begin "let file_descriptors = [|"; - Dynarray.iter (fun fd_bytes -> - Code.emit implementation `None {|"%s";|} fd_bytes - ) file_descriptors; - Code.emit implementation `None "|]"; - Code.emit implementation `End ""; - - Code.emit implementation `Begin "let get_file_descriptor name ="; - Code.emit implementation `None "match name with"; - Hashtbl.iter refl_file_db ~f:(fun ~key ~data -> - Code.emit implementation `None {s|| "%s" -> Some (file_descriptors.(%d)) |s} key data); - Code.emit implementation `None "| _ -> None"; - Code.emit implementation `End ""; - - Code.emit implementation `Begin "let get_service_descriptor name ="; - Code.emit implementation `None "match name with"; - Hashtbl.iter refl_service_db ~f:(fun ~key ~data -> - Code.emit implementation `None {s|| "%s" -> Some (file_descriptors.(%d)) |s} key data); - Code.emit implementation `None "| _ -> None"; - Code.emit implementation `End ""; - - Code.emit implementation `Begin "let services = ["; - Hashtbl.iter refl_service_db ~f:(fun ~key ~data:_ -> - Code.emit implementation `None {|"%s";|} key); - Code.emit implementation `End "]"; - - implementation diff --git a/src/plugin/emit.mli b/src/plugin/emit.mli index 44c9c96..aae3873 100644 --- a/src/plugin/emit.mli +++ b/src/plugin/emit.mli @@ -1,11 +1,4 @@ open Spec.Descriptor.Google.Protobuf val parse_proto_file: params:Parameters.t -> - scope:Scope.t -> type_db:Type_db.t -> - refl_service_db:(string, int) MoreLabels.Hashtbl.t -> - (FileDescriptorProto.t * int) -> string * Code.t - -val reflection : - file_descriptors:string Dynarray.t -> - refl_file_db:(string, int) MoreLabels.Hashtbl.t -> - refl_service_db:(string, int) MoreLabels.Hashtbl.t -> Code.t + scope:Scope.t -> type_db:Type_db.t -> FileDescriptorProto.t -> string * Code.t diff --git a/src/plugin/parameters.ml b/src/plugin/parameters.ml index a19b63b..f724482 100644 --- a/src/plugin/parameters.ml +++ b/src/plugin/parameters.ml @@ -10,7 +10,6 @@ type t = { singleton_record: bool; prefix_output_with_package: bool; singleton_oneof_as_option: bool; - reflection_map: bool; } let default = { @@ -23,7 +22,6 @@ let default = { singleton_record = false; prefix_output_with_package = false; singleton_oneof_as_option = true; - reflection_map = false; } let parse_option str = @@ -43,10 +41,9 @@ let parse parameters = | `Expr ("int64_as_int", (("true"|"false") as v)) -> { param with int64_as_int = (bool_of_string v) }; | `Expr ("int32_as_int", (("true"|"false") as v)) -> { param with int32_as_int = (bool_of_string v) }; | `Expr ("singleton_record", (("true"|"false") as v)) -> { param with singleton_record = (bool_of_string v) }; - | `Stmt "debug" -> { param with debug = true } - | `Expr ("prefix_output_with_package", (("true"|"false") as v)) -> { param with prefix_output_with_package = (bool_of_string v) } - | `Expr ("singleton_oneof_as_option", (("true"|"false") as v)) -> { param with singleton_oneof_as_option = (bool_of_string v) } - | `Stmt "reflection_map" -> { param with reflection_map = true } + | `Stmt "debug" -> { param with debug = true} + | `Expr ("prefix_output_with_package", (("true"|"false") as v)) -> { param with prefix_output_with_package = (bool_of_string v)} + | `Expr ("singleton_oneof_as_option", (("true"|"false") as v)) -> { param with singleton_oneof_as_option = (bool_of_string v)} | `Stmt "" -> param | _ -> failwith ("Unknown parameter: " ^ option) ) diff --git a/src/plugin/protoc_gen_ocaml.ml b/src/plugin/protoc_gen_ocaml.ml index 619be5e..0165c2d 100644 --- a/src/plugin/protoc_gen_ocaml.ml +++ b/src/plugin/protoc_gen_ocaml.ml @@ -27,57 +27,40 @@ let write response = |> Ocaml_protoc_plugin.Writer.contents |> output_string stdout -let parse_request params files_to_generate proto_files = + +let parse_request Plugin.CodeGeneratorRequest.{file_to_generate = files_to_generate; parameter = parameters; proto_file = proto_files; compiler_version = _} = + let params = Parameters.parse (Option.value ~default:"" parameters) in let target_proto_files = List.filter ~f:(fun Descriptor.FileDescriptorProto.{name; _} -> List.mem ~set:files_to_generate (Option.value_exn name) ) proto_files in let type_db = Type_db.init ~params proto_files in - let fds = Dynarray.create () in - let refl_service_db = Hashtbl .create 16 in - let refl_file_db = Hashtbl.create 16 in let result = - List.mapi ~f:(fun idx (proto_file : Descriptor.FileDescriptorProto.t) -> - let file_descriptor_bytes = let v = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto proto_file in - Ocaml_protoc_plugin.Writer.contents v |> String.escaped - in + List.map ~f:(fun (proto_file : Descriptor.FileDescriptorProto.t) -> let proto_file_name = Option.value_exn ~message:"All files must have a name" proto_file.name in - Hashtbl.add refl_file_db proto_file_name idx; - Dynarray.add_last fds file_descriptor_bytes; let scope = Scope.init ~module_name:(Type_db.get_module_name type_db proto_file_name) in - Emit.parse_proto_file ~params ~scope ~type_db ~refl_service_db (proto_file, idx) + Emit.parse_proto_file ~params ~scope ~type_db proto_file ) target_proto_files + |> List.map ~f:(fun (name, code) -> + (name, code) + ) in - let result_reflection = Emit.reflection ~file_descriptors:fds ~refl_file_db ~refl_service_db in (match params.debug with | true -> List.iter ~f:(fun (_, code) -> Printf.eprintf "%s\n%!" (Code.contents code)) result | false -> ()); - result, result_reflection + result let () = let request = read () in try - let Plugin.CodeGeneratorRequest.{parameter = parameters; file_to_generate = files_to_generate; proto_file = proto_files; _} = request in - let params = - Parameters.parse (Option.value ~default:"" parameters) - in - let (outputs, reflection) = parse_request params files_to_generate proto_files in + let outputs = parse_request request in let response_of_output (name, code) = Plugin.CodeGeneratorResponse.File.make ~name ~content:(Code.contents code) () in let response : Plugin.CodeGeneratorResponse.t = Plugin.CodeGeneratorResponse.make ~supported_features:1 ~file:(List.map ~f:response_of_output outputs) () in - write response; - if params.reflection_map then - let response_of_reflection = Plugin.CodeGeneratorResponse.make ~file:[Plugin.CodeGeneratorResponse.File. - make - ~name:"reflection_map.ml" - ~content:(Code.contents reflection) - (); - ] () in - write response_of_reflection - + write response with | Failure message -> (Printf.eprintf "%s\n" message; exit 1) From 0e515fc425b2a3c1fc3fd3da868faba6b380e048 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Mon, 10 Nov 2025 18:12:49 +0900 Subject: [PATCH 03/10] fixup! fixup: include metainfo instead generating files --- src/plugin/emit.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugin/emit.ml b/src/plugin/emit.ml index 3361d42..80000ae 100644 --- a/src/plugin/emit.ml +++ b/src/plugin/emit.ml @@ -457,9 +457,9 @@ let emit_metainfo implementation fd package_service_names = Code.emit implementation `None "val package_service_names : string list"; Code.emit implementation `EndBegin "end = struct"; Code.emit implementation `None {|let file_descriptor_proto = "%s"|} file_descriptor_bytes; - Code.emit implementation `None "let package_service_names = ["; + Code.emit implementation `Begin "let package_service_names = ["; List.iter ~f:(fun name -> Code.emit implementation `None "\"%s\";" name) package_service_names; - Code.emit implementation `None "]"; + Code.emit implementation `End "]"; Code.emit implementation `End "end" let parse_proto_file ~params ~scope ~type_db filedescriptorproto = From 4aa696fa5bb34440654d2934650a5a3573390cf7 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Mon, 10 Nov 2025 18:20:41 +0900 Subject: [PATCH 04/10] fixup! fixup! fixup: include metainfo instead generating files --- src/ocaml_protoc_plugin/spec.ml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ocaml_protoc_plugin/spec.ml b/src/ocaml_protoc_plugin/spec.ml index e601b79..6666664 100644 --- a/src/ocaml_protoc_plugin/spec.ml +++ b/src/ocaml_protoc_plugin/spec.ml @@ -236,3 +236,8 @@ let default_of_spec: type a. (a, scalar) spec -> a = function | String -> "" | Bytes -> Bytes.create 0 | Enum (module Enum) -> Enum.from_int_exn 0 + +module type Metainfo = sig + val file_descriptor_proto : string + val package_service_names : string list +end From 32907893d1097c4ee3358c28aa487b936886405e Mon Sep 17 00:00:00 2001 From: Nymphium Date: Mon, 10 Nov 2025 18:49:28 +0900 Subject: [PATCH 05/10] fixup! fixup! fixup! fixup: include metainfo instead generating files --- examples/echo/test.ml | 3 ++- src/ocaml_protoc_plugin/spec.ml | 1 + src/plugin/emit.ml | 12 +++++------- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/echo/test.ml b/examples/echo/test.ml index c5b9b00..ccb2beb 100644 --- a/examples/echo/test.ml +++ b/examples/echo/test.ml @@ -51,4 +51,5 @@ let () = let name = Echo.Echo.package_service_name in let request = mk_request () in let reply = do_request ~handler:handle_request request in - Printf.printf "Reply to %s: %s\n" name reply + Printf.printf "Reply to %s: %s\n" name reply; + assert (List.mem Echo.Echo.package_service_name Metainfo.package_service_names) diff --git a/src/ocaml_protoc_plugin/spec.ml b/src/ocaml_protoc_plugin/spec.ml index 6666664..711e9d0 100644 --- a/src/ocaml_protoc_plugin/spec.ml +++ b/src/ocaml_protoc_plugin/spec.ml @@ -238,6 +238,7 @@ let default_of_spec: type a. (a, scalar) spec -> a = function | Enum (module Enum) -> Enum.from_int_exn 0 module type Metainfo = sig + val file_name : string val file_descriptor_proto : string val package_service_names : string list end diff --git a/src/plugin/emit.ml b/src/plugin/emit.ml index 80000ae..5a9aec6 100644 --- a/src/plugin/emit.ml +++ b/src/plugin/emit.ml @@ -448,17 +448,15 @@ let emit_header implementation ~proto_name ~syntax ~params = (* Code.emit implementation `None "%s" (Code.append_deprecaton_if ~deprecated `Floating ""); *) () -let emit_metainfo implementation fd package_service_names = +let emit_metainfo implementation fd file_name package_service_names = let file_descriptor_bytes = let v = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto fd in Ocaml_protoc_plugin.Writer.contents v |> String.escaped in - Code.emit implementation `Begin "module Metainfo : sig"; - Code.emit implementation `None "val file_descriptor_proto : string"; - Code.emit implementation `None "val package_service_names : string list"; - Code.emit implementation `EndBegin "end = struct"; + Code.emit implementation `Begin "module Metainfo : Runtime'.Spec.Metainfo = struct"; + Code.emit implementation `None {|let file_name = "%s"|} file_name; Code.emit implementation `None {|let file_descriptor_proto = "%s"|} file_descriptor_bytes; Code.emit implementation `Begin "let package_service_names = ["; - List.iter ~f:(fun name -> Code.emit implementation `None "\"%s\";" name) package_service_names; + List.iter ~f:(fun name -> Code.emit implementation `None {|"%s";|} name) package_service_names; Code.emit implementation `End "]"; Code.emit implementation `End "end" @@ -506,7 +504,7 @@ let parse_proto_file ~params ~scope ~type_db filedescriptorproto = Code.append implementation implementation'; Code.emit implementation `None ""; - emit_metainfo implementation filedescriptorproto package_service_names; + emit_metainfo implementation filedescriptorproto proto_name package_service_names; let output_file_name = Type_db.get_module_name type_db proto_name From 4fb8d6edd7801be6d783164f1dad3bdbff58155a Mon Sep 17 00:00:00 2001 From: Nymphium Date: Thu, 11 Dec 2025 13:55:29 +0900 Subject: [PATCH 06/10] Change name Metainfo -> Service_info / Add service_info flag --- README.md | 1 + examples/echo/dune | 2 +- examples/echo/test.ml | 2 +- src/ocaml_protoc_plugin/spec.ml | 2 +- src/plugin/emit.ml | 2 +- src/plugin/parameters.ml | 3 +++ 6 files changed, 8 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 0e30ed7..cb656a9 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,7 @@ can generate the Ocaml code by running | singleton\_record | Messages with only one field will be wrapped in a record | `singleton_record=true` | false | | prefix\_output\_with\_package | Emit files prefixed with their package name. This allows multiple protofiles of the same name with different package names to be used | `prefix_output_with_package=true`[^5] | false | | singleton\_oneof\_as\_option | Oneof declarations only containing one field are mapped to a single optional field | singleton\_oneof\_as\_option=false | true | +| service\_info | Include file descriptors, source proto file path and list of its services in generated code | `service_info=true` | false | Parameters are separated by `;` diff --git a/examples/echo/dune b/examples/echo/dune index dc2d167..7bb7626 100644 --- a/examples/echo/dune +++ b/examples/echo/dune @@ -13,7 +13,7 @@ (deps (:proto echo.proto) (package ocaml-protoc-plugin)) (action - (run protoc -I %{read-lines:google_include} -I . "--ocaml_out=open=Google_types:." %{proto}))) + (run protoc -I %{read-lines:google_include} -I . "--ocaml_out=open=Google_types;service_info=true:." %{proto}))) (rule (deps test.exe) (action (ignore-stdout (run %{deps}))) diff --git a/examples/echo/test.ml b/examples/echo/test.ml index ccb2beb..63416ed 100644 --- a/examples/echo/test.ml +++ b/examples/echo/test.ml @@ -52,4 +52,4 @@ let () = let request = mk_request () in let reply = do_request ~handler:handle_request request in Printf.printf "Reply to %s: %s\n" name reply; - assert (List.mem Echo.Echo.package_service_name Metainfo.package_service_names) + assert (List.mem Echo.Echo.package_service_name Service_info.package_service_names) diff --git a/src/ocaml_protoc_plugin/spec.ml b/src/ocaml_protoc_plugin/spec.ml index 711e9d0..fbdcb4b 100644 --- a/src/ocaml_protoc_plugin/spec.ml +++ b/src/ocaml_protoc_plugin/spec.ml @@ -237,7 +237,7 @@ let default_of_spec: type a. (a, scalar) spec -> a = function | Bytes -> Bytes.create 0 | Enum (module Enum) -> Enum.from_int_exn 0 -module type Metainfo = sig +module type Service_info = sig val file_name : string val file_descriptor_proto : string val package_service_names : string list diff --git a/src/plugin/emit.ml b/src/plugin/emit.ml index 5a9aec6..33ec44c 100644 --- a/src/plugin/emit.ml +++ b/src/plugin/emit.ml @@ -452,7 +452,7 @@ let emit_metainfo implementation fd file_name package_service_names = let file_descriptor_bytes = let v = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto fd in Ocaml_protoc_plugin.Writer.contents v |> String.escaped in - Code.emit implementation `Begin "module Metainfo : Runtime'.Spec.Metainfo = struct"; + Code.emit implementation `Begin "module Service_info : Runtime'.Spec.Service_info = struct"; Code.emit implementation `None {|let file_name = "%s"|} file_name; Code.emit implementation `None {|let file_descriptor_proto = "%s"|} file_descriptor_bytes; Code.emit implementation `Begin "let package_service_names = ["; diff --git a/src/plugin/parameters.ml b/src/plugin/parameters.ml index f724482..6bbe6c9 100644 --- a/src/plugin/parameters.ml +++ b/src/plugin/parameters.ml @@ -10,6 +10,7 @@ type t = { singleton_record: bool; prefix_output_with_package: bool; singleton_oneof_as_option: bool; + service_info: bool; } let default = { @@ -22,6 +23,7 @@ let default = { singleton_record = false; prefix_output_with_package = false; singleton_oneof_as_option = true; + service_info = false; } let parse_option str = @@ -44,6 +46,7 @@ let parse parameters = | `Stmt "debug" -> { param with debug = true} | `Expr ("prefix_output_with_package", (("true"|"false") as v)) -> { param with prefix_output_with_package = (bool_of_string v)} | `Expr ("singleton_oneof_as_option", (("true"|"false") as v)) -> { param with singleton_oneof_as_option = (bool_of_string v)} + | `Expr ("service_info", (("true"|"false") as v)) -> { param with service_info = (bool_of_string v)} | `Stmt "" -> param | _ -> failwith ("Unknown parameter: " ^ option) ) From 4be542e0492e7a6ef39a47cbd9b6e2656fc4df38 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Thu, 1 Jan 2026 15:42:20 +0900 Subject: [PATCH 07/10] Move Service_info to Service --- src/ocaml_protoc_plugin/service.ml | 6 ++++++ src/ocaml_protoc_plugin/spec.ml | 6 ------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ocaml_protoc_plugin/service.ml b/src/ocaml_protoc_plugin/service.ml index 5faef56..fcccd35 100644 --- a/src/ocaml_protoc_plugin/service.ml +++ b/src/ocaml_protoc_plugin/service.ml @@ -28,3 +28,9 @@ let make_service_functions (type req) (type rep) ((module Request : Spec.Message with type t = req), (module Response : Spec.Message with type t = rep)) = Request.from_proto, Response.to_proto + +module type Service_info = sig + val file_name : string + val file_descriptor_proto : string + val package_service_names : string list +end diff --git a/src/ocaml_protoc_plugin/spec.ml b/src/ocaml_protoc_plugin/spec.ml index fbdcb4b..e601b79 100644 --- a/src/ocaml_protoc_plugin/spec.ml +++ b/src/ocaml_protoc_plugin/spec.ml @@ -236,9 +236,3 @@ let default_of_spec: type a. (a, scalar) spec -> a = function | String -> "" | Bytes -> Bytes.create 0 | Enum (module Enum) -> Enum.from_int_exn 0 - -module type Service_info = sig - val file_name : string - val file_descriptor_proto : string - val package_service_names : string list -end From 06de7c237bf830c01908190464ff75220f28c091 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Thu, 1 Jan 2026 15:42:51 +0900 Subject: [PATCH 08/10] Rename emit_metainfo to emit_service_info --- src/plugin/emit.ml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/plugin/emit.ml b/src/plugin/emit.ml index 33ec44c..894a3b3 100644 --- a/src/plugin/emit.ml +++ b/src/plugin/emit.ml @@ -448,11 +448,9 @@ let emit_header implementation ~proto_name ~syntax ~params = (* Code.emit implementation `None "%s" (Code.append_deprecaton_if ~deprecated `Floating ""); *) () -let emit_metainfo implementation fd file_name package_service_names = - let file_descriptor_bytes = let v = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto fd in - Ocaml_protoc_plugin.Writer.contents v |> String.escaped - in - Code.emit implementation `Begin "module Service_info : Runtime'.Spec.Service_info = struct"; +let emit_service_info implementation fd file_name package_service_names = + let file_descriptor_bytes = Spec.Descriptor.Google.Protobuf.FileDescriptorProto.to_proto fd |> Ocaml_protoc_plugin.Writer.contents |> String.escaped in + Code.emit implementation `Begin "module Service_info : Runtime'.Service.Service_info = struct"; Code.emit implementation `None {|let file_name = "%s"|} file_name; Code.emit implementation `None {|let file_descriptor_proto = "%s"|} file_descriptor_bytes; Code.emit implementation `Begin "let package_service_names = ["; @@ -504,7 +502,7 @@ let parse_proto_file ~params ~scope ~type_db filedescriptorproto = Code.append implementation implementation'; Code.emit implementation `None ""; - emit_metainfo implementation filedescriptorproto proto_name package_service_names; + emit_service_info implementation filedescriptorproto proto_name package_service_names; let output_file_name = Type_db.get_module_name type_db proto_name From 16eb33df2cf7610663e3e82254ac9faec544ee57 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Thu, 1 Jan 2026 15:43:03 +0900 Subject: [PATCH 09/10] Add tests for service_info --- test/dune | 10 ++++++++++ test/reflection.proto | 10 ++++++++++ test/reflection_parts.proto | 12 ++++++++++++ test/reflection_test.ml | 37 +++++++++++++++++++++++++++++++++++++ 4 files changed, 69 insertions(+) create mode 100644 test/reflection.proto create mode 100644 test/reflection_parts.proto create mode 100644 test/reflection_test.ml diff --git a/test/dune b/test/dune index 21e92b2..c519124 100644 --- a/test/dune +++ b/test/dune @@ -142,3 +142,13 @@ "--experimental_allow_proto3_optional" "--plugin=protoc-gen-ocaml=%{plugin}" "--ocaml_out=open=Google_types_pp;open=Test_runtime;annot=[@@deriving show { with_path = false }, eq]:." %{proto}))) + +(rule + (targets reflection.ml reflection_parts.ml) + (deps + (:plugin %{bin:protoc-gen-ocaml}) + (:proto reflection.proto reflection_parts.proto)) + (action + (run %{bin:protoc} -I %{read-lines:google_include} -I . + "--plugin=protoc-gen-ocaml=%{plugin}" + "--ocaml_out=open=Google_types_pp;service_info=true:." %{proto}))) diff --git a/test/reflection.proto b/test/reflection.proto new file mode 100644 index 0000000..4b4e9df --- /dev/null +++ b/test/reflection.proto @@ -0,0 +1,10 @@ +syntax = "proto3"; + +package test.reflection; +import "reflection_parts.proto"; + +service EmptyService { } + +service SomeService { + rpc Call (test.reflection_parts.Request) returns (test.reflection_parts.Response); +} diff --git a/test/reflection_parts.proto b/test/reflection_parts.proto new file mode 100644 index 0000000..588ab9c --- /dev/null +++ b/test/reflection_parts.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; + +package test.reflection_parts; +import "google/protobuf/empty.proto"; + +message Request { + uint64 i = 1; +} + +message Response { + google.protobuf.Empty empty = 1; +} diff --git a/test/reflection_test.ml b/test/reflection_test.ml new file mode 100644 index 0000000..d706d5b --- /dev/null +++ b/test/reflection_test.ml @@ -0,0 +1,37 @@ +open Ocaml_protoc_plugin + +module S = Reflection +module P = Reflection_parts + +let%test _ = + S.Service_info.file_name = "reflection.proto" + +let%test _ = + P.Service_info.file_name ="reflection_parts.proto" + +let%test "package service path" = + S.Service_info.package_service_names = [ "test.reflection.EmptyService"; "test.reflection.SomeService" ] + + +let%test_module "Construct service_info by itself" = (module + struct + open Google_types_pp.Descriptor.Google.Protobuf + let spec = Reader.create S.Service_info.file_descriptor_proto |> FileDescriptorProto.from_proto_exn + let fd = FileDescriptorProto.to_proto spec |> Writer.contents |> String.escaped + + let package = Option.value spec.package ~default:"" + + let%test "file_name" = + spec.name = Some S.Service_info.file_name + + let%test "file_descriptor_proto" = + fd = String.escaped S.Service_info.file_descriptor_proto + + let%test "package_service_names" = + let services = + spec.service + |> List.map + @@ fun ServiceDescriptorProto.{name; _} -> Option.fold name ~none:"" ~some:(Printf.sprintf "%s.%s" package) + in + services = S.Service_info.package_service_names + end) From f42ab399259635654b000207c286242749e76383 Mon Sep 17 00:00:00 2001 From: Nymphium Date: Thu, 1 Jan 2026 15:50:24 +0900 Subject: [PATCH 10/10] fixup! Add tests for service_info --- test/reflection_parts.proto | 5 +---- test/reflection_test.ml | 8 +++++--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/test/reflection_parts.proto b/test/reflection_parts.proto index 588ab9c..fc9cb90 100644 --- a/test/reflection_parts.proto +++ b/test/reflection_parts.proto @@ -1,12 +1,9 @@ syntax = "proto3"; package test.reflection_parts; -import "google/protobuf/empty.proto"; message Request { uint64 i = 1; } -message Response { - google.protobuf.Empty empty = 1; -} +message Response { } diff --git a/test/reflection_test.ml b/test/reflection_test.ml index d706d5b..d15b6a7 100644 --- a/test/reflection_test.ml +++ b/test/reflection_test.ml @@ -7,11 +7,13 @@ let%test _ = S.Service_info.file_name = "reflection.proto" let%test _ = - P.Service_info.file_name ="reflection_parts.proto" - -let%test "package service path" = S.Service_info.package_service_names = [ "test.reflection.EmptyService"; "test.reflection.SomeService" ] +let%test _ = + P.Service_info.file_name ="reflection_parts.proto" + +let%test _ = + List.is_empty P.Service_info.package_service_names let%test_module "Construct service_info by itself" = (module struct