I think it really depends on how you use this function, if you actually intend to process the result of this function, you’re then likely interested in the shape of this result. If you’re ok with a very light runtime overhead and some flexibility in the type safety, you could do something like that (heavily inspired by rescript-schema):
type rec parameterTypeKeys<_> =
| @as("string") String: parameterTypeKeys<string>
| @as("number") Number: parameterTypeKeys<float>
| @as("boolean") Boolean: parameterTypeKeys<bool>
@tag("success")
type parseResult<'params> =
| @as(true) Success({parameters: 'params})
| @as(false) Failure({reply: string})
module S: {
type t<'a>
type helper = {field: 'a. parameterTypeKeys<'a> => 'a}
let object: (helper => ({..} as 'a)) => t<'a>
} = {
type parameterDefinition = {
name: string,
@as("type") type_: string,
}
type t<'a> = array<parameterDefinition>
type helper = {field: 'a. parameterTypeKeys<'a> => 'a}
external field: parameterTypeKeys<'a> => 'a = "%identity"
external schemaToDictString: {..} => dict<string> = "%identity"
let object = f => {
f({field: field})
->schemaToDictString
->Dict.toArray
->Array.map(((name, type_)) => {name, type_})
}
}
@scope("utils") @val
external parseParametersFromArguments: (
S.t<{..} as 'a>,
array<string>,
) => parseResult<'a> = "parseParametersFromArguments"
You’d then have type safety on the result:
let res = parseParametersFromArguments(
S.object(s =>
{
"index": s.field(Number),
"foo": s.field(String),
}
),
["index:1", "foo:bar"],
)
switch res {
| Success({parameters}) if parameters["index"] == 1.0 =>
Console.log("great success!")
| _ => Console.log("oh nooo!")
}
Depending on what you’re looking for, this could be another solution.