Quantcast
Channel: ReScript Forum - Latest posts
Viewing all articles
Browse latest Browse all 2592

Promise of result--why or why not?

$
0
0

Hi folks,

I have a related question about all this. I’m currently in a Node.JS backend function.
A lot of things can go wrong in my request handler. Some of these things I know upfront, so I tried to model it a bit.

open FirebaseFunctions.Logger
open FirebaseFunctions.Https

type hookErrorType =
  | InvalidRequestMethod
  | MissingRequestBody
  | MissingStripeHeader
  | ConfigurationError
  | WebhookSignatureVerificationFailed({err: Error.t})

// Custom exception used in the promise
exception HookError(hookErrorType)

let stripeSignatureHeader = "stripe-signature"

let getNonEmptyString = (s: string) => {
  switch Nullable.make(s)->Nullable.toOption {
  | None => None
  | Some(v) if v == "" => None
  | Some(v) => Some(v)
  }
}

let validateRequest = request => {
  if request->method != #POST {
    Promise.reject(HookError(InvalidRequestMethod))
  } else {
    let rawBodyOpt = request->rawBody->getNonEmptyString
    let stripeHeaderOpt = request->headers->Dict.get(stripeSignatureHeader)
    switch (rawBodyOpt, stripeHeaderOpt) {
    | (None, _) => Promise.reject(HookError(MissingRequestBody))
    | (Some(_), None) => Promise.reject(HookError(MissingStripeHeader))
    | (Some(rawBody), Some(stripeHeader)) => Promise.resolve((rawBody, stripeHeader))
    }
  }
}

let getEndpointSecret = () => {
  switch Common.stripeEndpointSecret->Nullable.make->Nullable.toOption {
  | None => Promise.reject(HookError(ConfigurationError))
  | Some(endpointSecret) => Promise.resolve(endpointSecret)
  }
}

let stripe = Stripe.make(Common.stripe_private_key)

let constructStripeEvent = (rawBody, signature, endpointSecret) => {
  try {
    let event =
      stripe->Stripe.constructEvent(~payload=rawBody, ~header=signature, ~secret=endpointSecret)->Ok

    if event->Nullable.make->Nullable.toOption->Option.isNone {
      Js.Exn.raiseError("Stripe event was null")
    } else {
      Promise.resolve(event)
    }
  } catch {
  | Js.Exn.Error(obj) => Promise.reject(HookError(WebhookSignatureVerificationFailed({err: obj})))
  }
}

let savePaymentEvent:Promise<unit> = (event: Stripe.webHookEvent) => {
  // Saving to Firebase, this will happen via a promise
  Promise.resolve()
}

// Main entry point
let paymentHook = onRequest(
  ~opts={
    region: Domain.firebaseRegion,
  },
  ~handler=async (request, response) => {
    try {
      let (rawBody, stripeHeader) = await validateRequest(request)
      let endpointSecret = await getEndpointSecret()
      let event = await constructStripeEvent(rawBody, stripeHeader, endpointSecret)
      logger->info("Stripe event", ~data={"event": event})
    } catch {
    | HookError(errorType) => () // deal with things I know could go wrong
    | exn => () // deal with unexpected errors
    }
  },
)

I tried to use promises all the way, because some steps in my pipeline need to be promises. Other stuff could be synchronous and use the Result type, but that doesn’t mix well the moment I need a promise.

Is it sensible to go this promise route? Any advice on this?


Viewing all articles
Browse latest Browse all 2592

Trending Articles