ahrefs / atd

Static types for JSON APIs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Unify output of `atdgen -bs` and `atdgen -j`

bbenne10 opened this issue · comments

I'm currently writing a fullstack Reason application: Native code as the server (using Opium) and JS via Melange on the frontend.

Right now, the bucklescript output and json output do not have the same api. Notably, the _of_string and _to_string functions are missing from the bucklescript output, which makes transparent usage between the frontend and backend code impossible (or at least...much more verbose in the specific and impossible in the generic).

However, I think this could be done pretty trivially in the atdgen emitters. For instance, assuming the following saved as thing.atd:

type thing= {
  ~id <ocaml default="0">: int;
  foo: string;
  bar: int;
}

atdgen -j spits out these types (actual output modified for brevity):

type thing = Thing_t.thing = { id: int; foo: string; bar: int }
val write_thing :  Buffer.t -> thing -> unit
val string_of_thing : ?len:int -> thing -> string
val read_thing : Yojson.Safe.lexer_state -> Lexing.lexbuf -> thing
val thing_of_string : string -> thing

while atdgen -bs gives us the following:

type thing = Thing_t.thing = { id: int; foo: string; bar: int }
val read_thing :  thing Atdgen_codec_runtime.Decode.t
val write_thing :  thing Atdgen_codec_runtime.Encode.t

The string_of_thing and thing_of_string functions are extremely helpful, in that I can map over then in module entrypoint Thing.re to unify my interaction and abstract away usage of either YoJson.Safe.t or Atdgen_runtime.{Decode,Encode}.t entirely.

The generated function would be pretty trivial (note that it accepts len for parity with the atdgen -j output, but discards it):

let thing_of_string (~len=1024, input) =
  let json = Js.Json.parseExn input in
  let thing: Thing_t.t = Atdgen_codec_runtime.Decode.decode Thing_bs.read_thing json in
  thing

There are certainly things I'm overlooking here (Maybe the source of the underlying Js.Json type is a problem? since it will come from either ReScript or Melange? Maybe the tooling is a problem because we can't guarantee that Js.Json is available all the time in the runtime (since the rescript tooling is no longer ocaml native?)

what will be the unified signatures of _of_string and _to_string for type 'a foo = { bar : 'a }?

I'm genuinely unsure. I hadn't considered that the type abstraction would mean you'd need a lexer to define what 'a would be... If it isn't obvious, my use case is not particularly generic (though that doesn't mean it shouldn't be taken into account).

Can we mirror the _j example and accept two lexers? Or just...not emit the functions in the ambiguous cases (maybe after emitting a warning to stdout)? I see the difficulty here, but I do think that there's benefit in the simple case to be able to hide the complexity a bit.