[@bs.optional] and fields with null


#1

Hi,

I’m doing my first baby steps with Reason and getting really excited!

My first playground is using it in an AWS Lambda project. Found bs-aws-lambda which looked really promising to quickly get going with type safety. The challenge I’m facing now, seems to be related to how [@bs.optional] in AwsLambda.APIGatewayProxy.Event.t handles fields with null, though I’d be surprised if this isn’t my fault… :grimacing:

TLDR; I’m trying to gracefully extract the query parameter name, and create a greeting based on whether that parameter exists or not.

# greeter.re

open AwsLambda.APIGatewayProxy;
open Belt;

let sayHello = (event: Event.t): string => {
  let nameParam =
    event
    ->Event.queryStringParametersGet
    ->Option.flatMap(dict => dict->Js.Dict.get("name"));

  switch (nameParam) {
  | Some(name) => "hello there " ++ name ++ "!"
  | None => "no idea what your name is :/"
  };
};

# handler.js

const greeter = require('./greeter.bs')

module.exports.hello = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: greeter.sayHello(event)
    })
  };
};

event example with query parameter in place (simplified for brevity):

{ 
  headers: { .. },
  path: '/hello',
  queryStringParameters: { name: 'Santa' },
  body: null
}

Everything works just as expected with the above. The challenge is when no query parameters are passed in at all in the incoming HTTP request;

{ 
  headers: { .. },
  path: '/hello',
  queryStringParameters: null,
  body: null
}

That results in the following error:

Cannot read property 'name' of null

In other words, the Js.Dict.get() in greeter.re is trying to operate on a null value rather than an actual dictionary :disappointed:

Any thoughts what I should rather do to avoid this explosion?


#2

Forgot to mention, I’ve got it to work as I’d expect by ensuring the event.queryStringParameters field is not present, rather than having a null value:

module.exports.hello = async (event, context) => {
  if (event.queryStringParameters === null) {
    delete event.queryStringParameters;
  }

  return {
    statusCode: 200,
    body: JSON.stringify({
      message: greeter.sayHello(event)
    })
  };
};

Though this feels like a less-than-ideal solution.


#3

I think [@bs.optional] only works to mark fields that can be omitted, not fields that can contain null instead of a proper value.

If the field is supposed to be nullable, the type queryStringParameters: Js.Dict.t(string) should be changed. What is the AWS documentation for the event object? Do they say if the queryStringParameters can be null?


#4

Sadly I haven’t been able to find per field AWS documentation on that API Gateway event object. The nearest thing I’ve found, is an Lambda example from AWS that handles queryStringParameter being null: https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-create-api-as-simple-proxy-for-lambda.html#api-gateway-proxy-integration-lambda-function-nodejs

The TypeScript defs has null is a valid value though, rather than the field’s presence being optional: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/aws-lambda/index.d.ts#L65

Are there any other ways than using [@bs.optional] that should have been used for queryStringParameters in bs-aws-lambda?


#5

Yes. The type should be changed to use https://bucklescript.github.io/docs/en/null-undefined-option#solution-more-sophisticated-undefined-null-interop

queryStringParameters: Js.Dict.t(string) would become queryStringParameters: Js.Nullable.t(Js.Dict.t(string)).

Do you want to make a PR? Or I will make the change on bs-aws-lambda tomorrow.


#6

PR has been submitted.

For future reference if anyone else faces the same kind of issue, this code now works as expected in all scenarios:

let sayHello = (event: Event.t): string => {
  let nameParam =
    event
    ->Event.queryStringParametersGet
    ->Js.Nullable.toOption
    ->Option.flatMap(dict => dict->Js.Dict.get("name"));

  switch (nameParam) {
  | Some(name) => "hello there " ++ name ++ "!"
  | None => "no idea what your name is :/"
  };
};

#7

On what repo did you submitted the PR? I don’t see anything on https://github.com/ahrefs/bs-aws-lambda