Reputation: 3494
Using basic node.js, in http.createServer
I am taking the request
, and taking the path
from the request. I have a router
object, keyed by paths, with values being handler functions.
// index.ts
export interface DataObject {
queryStringObject: ParsedUrlQuery;
method: string;
headers: IncomingHttpHeaders;
payload: any;
}
export type HandlerCallback = (statusCode: number, payload?: Object) => any;
export type Handler = (data: DataObject, callback: HandlerCallback) => any;
interface Router {
ping: Handler;
users: Handler;
adminUsers: Handler;
tokens: Handler;
}
// Define request router
const router: Router = {
ping: handlers.ping,
users: handlers.users,
adminUsers: handlers.adminUsers,
tokens: handlers.tokens,
};
export enum RestMethods {
get = 'get',
post = 'post',
put = 'put',
delete = 'delete',
}
export type RestHandler = {
[method in RestMethods]: Handler;
};
Then, I have the file where handlers
is defined:
interface Handlers {
users: Handler;
adminUsers: Handler;
tokens: Handler;
notFound: Handler;
ping: Handler;
}
interface SubHandlers {
users: RestHandler;
adminUsers: RestHandler;
tokens: RestHandler;
}
export const handlers: Handlers = {
users: (data, callback) => {
const acceptableMethods = ['get', 'post', 'put', 'delete'];
if (acceptableMethods.indexOf(data.method) !== -1) {
subHandlers.users[data.method](data, callback);
}
},
(...)
I get a typescript
error on the line subhandlers.users[data.method]...
Error:(62, 14) TS7053: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'Router'. No index signature with a parameter of type 'string' was found on type 'Router'.
data.method
is a string
because DataObject
defines it that way (an incoming request could have anything in method
), and subhandlers.users
is of type RestHandler
, which is an object with keys in the enum RestMethods
. But I am doing the check acceptableMethods.indexOf(data.method) !== -1
... How can I make this check so that Typescript can realize that data.method has been restricted to only the strings that are in the enum?
Upvotes: 1
Views: 227
Reputation: 327624
There are two issues here; one is that acceptableMethods
is inferred to be of type string[]
, whereas you want the compiler to keep track of the string literal values it contains; you can fix that easily enough with a const
assertion as in:
const acceptableMethods = ["get", "post", "put", "delete"] as const;
The second issue is that TypeScript does not understand or expect that, given an array array
of type T[]
and a value val
, checking array.indexOf(val) !== -1
should narrow val
to type T
. Luckily you can get this behavior by making a user-defined type guard function:
function arrayIncludes<T>(
arr: ReadonlyArray<T>,
val: any
): val is T {
return arr.indexOf(val) !== -1;
}
Now you can use arrayIncludes()
in place of the indexOf
check, and it should work for you:
export const handlers: Handlers = {
users: (data, callback) => {
const acceptableMethods = ["get", "post", "put", "delete"] as const;
if (arrayIncludes(acceptableMethods, data.method)) {
subHandlers.users[data.method](data, callback); // okay
}
}
};
Does that help? Good luck!
Upvotes: 4