Search code examples
typescript

Typescript method decorator to ensure a specific method return type


I have made a few decorators to make routing easy with express (e.i. @Get(), @Post) which are used to map class functions to a route.

I would like the targeted method's return type to systematically be of type HttpResponse and have ts or the IDE throw an error when another type is returned:

export type HttpResponse = { data?: any; status?: number; } | object;

For instance:

@Post()
login(email: string, password: string) : object {   // <-- highlights method as returning the wrong type
   return {};
}

@Post()
async register(email: string, password: string) : Promise<HttpResponse> {   // <-- all good
   return {};
}

Can this be achieved? Could this be done using a custom tsconfig rule?

PS: Decorators are very knew to me and I might be extrapolating how decorators can be used.


Solution

  • I will assume your Post decorator looks like this

    declare function Post(): MethodDecorator
    

    If you look what MethodDecorator type in TS definitions looks like, you'll find

    declare type MethodDecorator = <T>(
        target: Object,
        propertyKey: string | symbol,
        descriptor: TypedPropertyDescriptor<T>,
    ) => TypedPropertyDescriptor<T> | void
    

    It's not clear to me, why this type is not generic (the function it declares it generic, but the type itself is not), TS developpers must've had their reasons for this. Anyway, since you worked with decorators, you know, that this third argument is the descriptor of the property you attach your decorator to, here it's of type WhateverTypeIsUsedToTypeDescriptors<T>. You can declare your own decorator type that only expects this property to be a function that returns HttpResponse by replacing this T with your type:

    type HttpMethod = (...args: any[]) => HttpResponse
    type HttpMethodDecorator = (
        target: Object,
        propertyKey: string | symbol,
        descriptor: TypedPropertyDescriptor<HttpMethod>,
    ) => TypedPropertyDescriptor<HttpMethod> | void
    
    declare function Post(): HttpMethodDecorator
    

    See how this works in TS playground: if I try to decorate a method that returns string instead of HttpResponse, it shows an error (for some reason it says that HttpMethod is not assignable to (email: string, password: string) => string although you probably would expect it to be vice versa, but whatever).

    That's the main idea, now to clean up a little bit you may want to make this type generic:

    type MethodDecorator<T extends Function> = (
        target: Object,
        propertyKey: string | symbol,
        descriptor: TypedPropertyDescriptor<T>,
    ) => TypedPropertyDescriptor<T> | void
    
    declare function Post(): MethodDecorator<(...args: any[]) => HttpResponse>
    

    Also don't forget to let it accept promises

    declare function Post(): MethodDecorator<(...args: any[]) => HttpResponse | Promise<HttpResponse>>
    

    Heck, you could even type the function's arguments or basically anything, enjoy :P

    Also I'm not sure why you want TS to highlight login as returning the wrong type, because it returns object and in HttpResponse you have whatever | object so it shouldn't really be an error