In bindings is there any way to refactor out Js.Nullable.t and Js.Nullable.toOption in a returned record type?

data.js (stub representing returning a row from database)

exports.userFromDb = function () {
  return {
    userName: "kevin",
    email: "me@example.com",
    // email: null,
  }
}

file.res

type externalUser = {userName: string, email: Js.Nullable.t<string>}
@module("./data.js") external userFromDb: unit => externalUser = "userFromDb"

switch Js.Nullable.toOption(userFromDb().email) { // <--- YUCK
| Some(email) => Js.log2("sending email to", email)
| None => Js.log("error: user doesnt want email")
}

I know there’s a @return(nullable) but it doesn’t seem to work with the fields of returned records. Is there any way to get rid of this noise without getting too crazy?

If externalUser is likely to just be an intermediate type, is this the idiomatic way of converting it using the same Js.Nullable.toOption?

// conversion to friendlier user record type
type internalUser = {userName: string, email: option<string>}
let user: internalUser = {
  userName: userFromDb().userName,
  email: Js.Nullable.toOption(userFromDb().email), // <--- HERE
}

switch user.email {
| Some(email) => Js.log2("sending email to", email)
| None => Js.log("error: user doesnt want email")
}

Thanks

I treat the data from DB as foreign for my application and pass it through rescript-struct first to ensure that it represents what I expect. Also, it allows mapping data to a more convenient format to work, e.g. use option instead of nullable.

Validating whats coming from the db seems very wasteful to me. I prefer to just validate it once with a test, rather than potentially trillions of times in production.

Do your test correlate with your types somehow? As in, is there a guarantee the tests will fail if the backend data doesn’t match your types? If so, you can probably push the checks to test time.

One other variant is to generate the types by the GraphQL/OpenRPC schema. That way, depending on how you deploy, you can skip runtime checks as well. But are there any solutions that generate ReScript (or even OCaml) code?

This feels like an exaggeration to me. You should decode your external data once it enters you app, not all the time.

Well, in theory, if you trust your data (e.g., if you’re able to generate types by schema), you can massage it without decoding. Pedantically speaking, these are two separate concerns anyway, even though it’s convenient to lump the code for both together.

Unless I’m using some kind of ORM or code generator of course I’d test the heck out of the boundary between the app and the database.

I’m talking about the lifetime of the app, not something like per request.

There’s a related discussion, but no progress for now.

Looks like the most popular way to generate ReScript code is by using string templates. I haven’t seen any tools to work with ast directly, excluding the ocaml ones.

It can parse two nested objects with 8 fields ~3_000_000 times per second (benchmark). I’ve decided that it’s definitely won’t be a bottleneck in my app.
But I’d like to replace it with a codegen solution in the future.

1 Like