mransan / ocaml-protoc

A Protobuf Compiler for OCaml
https://mransan.github.io/ocaml-protoc/
MIT License
179 stars 33 forks source link

Multiple ppx annotations for one type? #194

Open Lupus opened 1 year ago

Lupus commented 1 year ago

I'm trying to build a flow with some validation of protobuf types for my Twirp generator (proprietary, uses custom binary to manipulate the compilerlib library of ocaml-protoc). The most optimal way that I found is to transform some per-field options from protobuf spec into ocaml_type_ppx, that is further processed by ocaml-protoc and resulting annotation gets emitted along with generated OCaml types. compilerlib provides me with grouped protos, that I traverse to collect per-field validation rules and emit single deriving annotation with all the rules passed as the arguments. But when I tried to implement the actual ppx to generate the validation code - I ran into an issue with recursive types. Only one type out of recursive group needs to have the deriving annotation, otherwise ppxlib calls the deriver multiple times for a whole group or recursive types. See this discuss thread for more context. The proposed solution is to have only one deriving annotation per group of recursive types and encode rest of validation metadata in custom annotations to each type. This works well with ppxlib, but requires an ugly hack - I'm passing several ppx annotations via ocaml_type_ppx concatenating them with ][@@ in an SQL injection style... I doubt that's the designed way to add multiple ppx to single type, although it works right now. Wanted to discuss if this is good enough and will likely work in future versions, or some separate API might be implemented to allow multiple ppx annotations to be specified for resulting OCaml type in a clean way.

Why ppx for validation in first place? I tried to find a way to implement custom codegen on top of compilerlib alone, but that seems to be quite complicated. One of the goals is to mark certain fields as required, so that there are no option wrappings in the resulting types. So for every message type I want to generate a type that is the same or a bit different with some functions to go from one to another. And code for generating types is large and depends on compilerlib internals that are not feasible to be exported as some public API. So I decided to pass some annotations down to OCaml types instead and handle them via ppx for the boilerplate codegen. This model gives somewhat clean separation of concerns.

c-cube commented 1 year ago

I don't have time for this right now, but I'd certainly be interested in a PR.

There could be a -ocaml_deriving_ppx option that can be repeated, like so:

$ ocaml-protoc foo.pb -ocaml_deriving_ppx "show {with_path=false}" -ocaml_deriving_ppx eq

and it's collect all the deriving arguments into one [@@deriving show {with_path=false}, eq]?

Lupus commented 1 year ago

So far we have a workaround in our binary, that's using the compilerlib. Once it breaks, maybe we'll invest into a better solution 😁