Recently I’ve been playing around with regex to consume some info from query parameters and noticed what I think is an inconsistency between what js can return and what ReScript types results as.
Issue
Given this example regex:
let regex = RegExp.fromString("(([a-z]+)n)?([a-z]+)")
As expected, when all the groups match, we get a complete array of strings.
switch (regex->RegExp.exec("applesnpears")) {
// results is typed as "array<string>"
| Some(results) => Console.log(results) // logs [ "applesnpears", "applesn", "apples", "pears" ]
| None => ()
}
However, when not all the groups match (thanks to optional groups), js will return an array of string or undefined.
switch (regex->RegExp.exec("pears")) {
// results is typed as "array<string>"
| Some(results) => Console.log(results) // logs [ "pears", undefined, undefined, "pears" ]
| None => ()
}
If I were to manipulate the string some way (String.split or the like, the app would error out).
ReScript Playground showing this
Current workaround
To work around this, I would have to know that these optionals may be undefined, and use something like a Nullable to get to what I would expect, an option.
type stairs = {
first: string,
rest: option<string>
}
let regex = RegExp.fromString("(([a-z]+)n)?([a-z]+)")
let parseQuery = (queryStr) => {
switch (regex->RegExp.exec("applesnpears")) {
| Some([_, _, nullableRest, first]) =>
Some({
first,
rest: nullableRest->Nullable.make->Nullable.toOption
})
| None
| [] => None
}
}
Expected logic
I would expect that RegExp.Result.t
was typed to be array<option<string>>
such that you had to manage the cases that match and which that don’t.
type stairs = {
first: string,
rest: option<string>
}
let regex = RegExp.fromString("(([a-z]+)n)?([a-z]+)")
let parseQuery = (queryStr) => {
switch (regex->RegExp.exec("applesnpears")) {
| Some([_, _, rest, Some(first)]) => Some({ first, rest })
| None
| [] => None
}
}