How to represent intersection types while binding typescript definitions

I’m trying to get my head around binding for interop with a library using its TypeScript definitions.
I got stuck while trying to bind a type declared as follows:

export type PromiseResult<D, E> = D & {$response: Response<D, E>};

Currently, on the Rescript side, I have something that goes like this:

module Request = {
  type t<'d, 'e>

  type promiseResult<'d, 'e> = 'd

  @send external promise: t<'d, 'e> => promise<promiseResult<'d, 'e>> = "promise"
}

I would like type promiseResult<'d, 'e> = 'd to have the shape of the intersection type expressed as the TypeScript one. How it’d be the most idiomatic way to do so on Rescript? I’d appreciate any help you can provide. :3

What library / types are you trying to bind to? - I’m not really sure, what the type parameters should encode.

But I’d say you are looking for @get in this case? (see the docs):

@get
external response: promiseResult<'d, 'e> => Response.t<'x, 'y> = "$response"

This allows you to access a prop $response in any value of type promiseResult<'d, 'e>. (Since this is a binding, you need to make sure, that any value having this type, actually has a prop with this name. Otherwise, you’ll get issues during runtime.)

Example in Playground

1 Like

When the TS type starts getting complicated with it’s unions / intersections, I find keeping it opaque and using external for access the easiest way to represent it

module Request = {
  type t<'d, 'e>
  type response<'d, 'e>

  external value: t<'d, 'e> => 'd = "%identity"
  @get external response: t<'d, 'e> => response<'d, 'e> = "$response"
}

let req: Request.t<'d, 'e>

req->Request.value
req->Request.response
2 Likes

Apologies for the lack of detail. I’m trying to create bindings for the getSecretValue method on the ugly SecretsManager client of the aws-sdk.

This is what I have so far:

type t

type makeOptions = {@as("Region") region: string}

@new @module("aws-sdk") external make: makeOptions => t = "SecretsManager"

type getSecretValueOptions = {}

type awsError = {
  message: string,
  code: string,
}

type getSecretValueResponse = {
  @as("ARN") arn?: string,
  @as("Name") name?: string,
  @as("VersionId") versionId?: string,
  @as("SecretString") secretString?: string,
}

module Request = {
  type t<'d, 'e>

  type promiseResult<'d, 'e> = 'd

  @send external promise: t<'d, 'e> => promise<promiseResult<'d, 'e>> = "promise"
}

@send
external getSecretValue: (t, getSecretValueOptions) => Request.t<getSecretValueResponse, awsError> =
  "getSecretValue"

Do you think I’m moving in the right direction? I’m also struggling to safely handle the exception it can throw, AWSError, which is just an object. I’m aware I’m fixating too much on trying to replicate its Typescript definitions, but I’m a beginner on Rescript, so I thought it was a good idea to use those types as a reference.

This is what I have so far using the bindings:

type res = Found(string) | NotFound | Other(Js.Exn.t)

let handler = async (event: createKeyEvent): createKeyResponse => {
  let sm = SecretsManager.make({region: Shared.defaultRegion})
  let res = switch await SecretsManager.getSecretValue(sm, {})->SecretsManager.Request.promise {
  | res => Found(res.secretString)
  // if obj is aws AND it has the code "ResourceNotFound", I would like to return a NotFound variant.
  // if it's other than that, I would like to return Other(Js.Exn.t)
  | exception Js.Exn.Error(obj) => NotFound
  }
  // ...
}

But I haven’t found a way to check the exception type. I have looked inside the Js module, and the only thing I can see it’d help is to check for the keys inside that object, but I’m not sure if that is a good idea either.

Thanks a lot, and sorry if that was too much help to ask.

You can look for the existence of Error.code and switch on that

type res = Found(string) | NotFound | Other(Js.Exn.t)

@get external code: Js.Exn.t => option<string> = "code"

// ...
  let res = switch await SecretsManager.getSecretValue(sm, {})->SecretsManager.Request.promise {
  | res => Found(res.secretString)
  | exception Js.Exn.Error(exn) =>
    switch exn->code {
      | Some("NOT_FOUND") => NotFound
      | _ => Other(exn)
    }
  }

This could also be a case for Polymorphic Variant

1 Like