Here's a different approach to handle this:
enum Endpoint {
CUSTOMERS = '/customers/:customerId',
PRODUCTS = '/products'
}
type CustomersEndPoint = {
endpoint: Endpoint.CUSTOMERS;
payload: {
customer_id: number,
name: string
};
}
type ProductsEndpoint = {
endpoint: Endpoint.PRODUCTS;
}
type Endpoints = CustomersEndPoint | ProductsEndpoint;
function makeRequest<Endpoint extends Endpoints['endpoint']>(...args: Extract<Endpoints, {endpoint: Endpoint}> extends {payload: infer Payload} ? [endpoint: Endpoint, payload: Payload] : [endpoint: Endpoint]) {
return {};
}
makeRequest(Endpoint.CUSTOMERS); // will result in an error
makeRequest(Endpoint.CUSTOMERS, {customer_id: 1, name: 'John Doe'}); // will succeed
makeRequest(Endpoint.PRODUCTS); // will succeed
makeRequest(Endpoint.PRODUCTS, 'param'); // will result in an error
The makeRequest function is a generic TypeScript function that accepts multiple arguments. Let's analyze its signature:
<Endpoint extends Endpoints['endpoint']>: This generic type parameter named Endpoint extends the endpoint property of the Endpoints type, which is a discriminated union type comprising CustomersEndPoint and ProductsEndpoint.
...args: Extract<Endpoints, {endpoint: Endpoint}> extends {payload: infer Payload} ? [endpoint: Endpoint, payload: Payload] : [endpoint: Endpoint]: This rest parameter args represents the varying number of arguments passed to the function. It utilizes TypeScript's conditional types to ascertain the type of args based on the value of Endpoint:
Extract<Endpoints, {endpoint: Endpoint}> isolates the specific subtype of Endpoints with an endpoint property matching the value of Endpoint. This aids in deducing the type of the payload for that particular endpoint.
extends {payload: infer Payload} is a conditional type that verifies if the extracted subtype contains a payload property, and if so, infers the type of the payload property as Payload.
? [endpoint: Endpoint, payload: Payload] : [endpoint: Endpoint] signifies the return type of the function. If the Payload type is inferred (i.e., if the endpoint has a payload), the return type is a tuple with the endpoint and payload elements. Conversely, if no Payload type is inferred (i.e., if the endpoint lacks a payload), the return type is a tuple with only the endpoint as its element.