I overcomplicated things a bit by using a generalized abstract data type (or GADT) for parameterTypeKeys and objects for the parsing result, objects being the structural equivalent of records in ReScript. Don’t worry, this kind of type shenanigans is quite uncommon in ReScript and is just needed here because of the very dynamic nature of the javascript function you’re binding to.
Here is a simplified version that doesn’t require GADTs and that allows both objects and records for the result:
@tag("success")
type parseResult<'params> =
| @as(true) Success({parameters: 'params})
| @as(false) Failure({reply: string})
module S: {
type t<'a>
type helper = {string: string, float: float, int: int, boolean: bool}
let object: (helper => 'a) => t<'a>
} = {
type parameterDefinition = {
name: string,
@as("type") type_: string,
}
type t<'a> = array<parameterDefinition>
type helper = {string: string, float: float, int: int, boolean: bool}
let string = "string"
external unsafeStringToFloat: string => float = "%identity"
let float = "number"->unsafeStringToFloat
external unsafeStringToInt: string => int = "%identity"
let int = "number"->unsafeStringToInt
external unsafeStringToBool: string => bool = "%identity"
let boolean = "boolean"->unsafeStringToBool
external schemaToDictString: 'a => dict<string> = "%identity"
let object = f => {
f({string, float, int, boolean})
->schemaToDictString
->Dict.toArray
->Array.map(((name, type_)) => {name, type_})
}
}
@scope("utils") @val
external parseParametersFromArguments: (
S.t<'a>,
array<string>,
) => parseResult<'a> = "parseParametersFromArguments"
You use it like that:
type myRecord = {
index: int,
foo: string,
}
let parseMyRecord = args =>
parseParametersFromArguments(
S.object(s => {
index: s.int,
foo: s.string,
}),
args,
)
switch parseMyRecord(["index:1", "foo:bar"]) {
| Success({parameters: {index: 1}}) => Console.log("great success!")
| _ => Console.log("oh nooo!")
}