When working with a generic function and aiming to have the compiler automatically deduce the type arguments based on the function arguments, it is crucial to ensure a straightforward relationship between the function parameter types and the generic type parameter. The compiler needs a clear way to "invert" a complex type F
to infer T
from a value of type F<T>
.
The types ValidateMiddlewares
and ValidateController
in this context are recursive conditional types, making it challenging for the compiler to perform the desired inversion. To address this, you can specify ValidateMiddlewares<T>
and ValidateController<T, U>
as assignable to T
when T
is correct, otherwise not. By utilizing an intersection like T & ValidateXXX<T>
, the compiler can infer T
as the input type and verify it against ValidateXXX<T>
effectively.
If the check fails, resulting in an intersection with the never
type, the error message might not be ideal. Various approaches can address this issue, but for brevity, an intersection as shown below proves sufficient:
const route = <Middlewares extends MiddlewareArray, Controller extends BaseController<any>>(
path: string,
middlewares: Middlewares & ValidateMiddlewares<Middlewares>,
controller: Controller & ValidateController<Controller, Middlewares>,
) => {
// routing implementation
}
With this setup, the function behaves as expected, distinguishing between valid and invalid scenarios based on the specified types.
Playground link to code