import { expectAssignable , expectType , expectNotType } from 'tsd' ; import type { RequestHandler , Schema , Service } from '../src' ; type User = { name : string ; } ; const user = { name : 'it works' , } ; type SharedRequest = { headers : { authorization ? : string ; } ; } ; interface ExampleSchema extends Schema { paths : { 'GET /' : { request : SharedRequest & { querystring : { getQueryParam ? : boolean ; } ; headers : { authorization : string ; getHeader : string ; } ; body : { isNever : true ; } ; } ; response : { 200 : { content : User ; headers : { 'x-custom' : string ; } ; } ; } ; } ; 'POST /' : { request : SharedRequest & { body : { name : string ; } ; querystring : { postQueryParam ? : true ; } ; headers : { postHeader : string ; postHeader2 : string ; } ; } ; response : { 201 : { content : User ; headers : { postHeaderRes : string ; postHeaderRes2 : string ; } ; } ; } ; } ; 'PATCH /' : { request : SharedRequest ; response : { 204 : { content : 'ok' ; } ; } ; } ; } ; } interface GetAndPost { querystring : { query : string ; } ; body : { tagsString ? : string ; } ; } interface ExtendedSchema extends ExampleSchema { paths : ExampleSchema [ 'paths' ] & { 'PATCH /other' : { request : SharedRequest ; response : { 204 : { content : 'ok' ; } ; } ; } ; 'PATCH /other_empty' : { request : SharedRequest ; response : { 204 : { content : never ; } ; } ; } ; 'PUT /other' : { request : SharedRequest & { querystring : { getQueryParam ? : boolean ; } ; } ; response : { 200 : { content : User ; } ; } ; } ; 'GET /test' : { request : GetAndPost ; response : { 200 : { content : 'ok' ; } ; } ; } ; 'POST /test' : { request : GetAndPost ; response : { 200 : { content : 'ok' ; } ; } ; } ; 'PUT /NOT_HANDLED_BY_COMPLEX_HANDLER' : { response : { 200 : { } ; } ; } ; } ; } expectType < Service < ExampleSchema > > ( { 'GET /' : ( ) => { return undefined ; } , 'POST /' : ( ) => { } , } ) ; expectType < Service < ExampleSchema > > ( { 'GET /' : ( req , reply ) => { expectType < 'GET' > ( req . method ) ; expectType < 'GET' > ( reply . request . method ) ; expectType < never > ( req . body ) ; expectType < '/' > ( req . routerPath ) ; expectType < '/' > ( reply . request . routerPath ) ; return { name : 'works' , } ; } , 'POST /' : ( ) => { return { name : 'works' , } ; } , } ) ; expectType < Service < ExampleSchema > > ( { 'GET /' : ( req , reply ) => { const reply200 = reply . status ( 200 ) ; reply200 . send ( { } ) ; reply200 . __known ; expectAssignable < boolean | undefined > ( req . query . getQueryParam ) ; expectType < never > ( req . body ) ; req . headers . getHeader ; req . headers . postHeader ; const knownReply200 = reply200 . headers ( { 'x-custom' : '1' } ) ; return knownReply200 . send ( user ) ; } , 'POST /' : ( req , reply ) => { reply . status ( 200 ) ; req . query . getQueryParam ; req . headers . postHeader ; req . headers . getHeader ; expectType < true | undefined > ( req . query . postQueryParam ) ; expectType < string > ( req . body . name ) ; const fastifyReply = reply . status ( 201 ) ; expectType < 201 > ( fastifyReply . statusCode ) ; if ( Math . random ( ) ) { return fastifyReply . send ( user ) ; } const retWithHeader1 = fastifyReply . header ( 'postHeaderRes' , '1' ) ; if ( Math . random ( ) ) { return retWithHeader1 . send ( user ) ; } if ( Math . random ( ) ) { return retWithHeader1 . header ( 'postHeaderRes2' , '2' ) . send ( ) ; } return retWithHeader1 . header ( 'postHeaderRes2' , '2' ) . send ( user ) ; } , 'PATCH /' : ( req , reply ) => { reply . status ( 200 ) ; req . query . getQueryParam ; return reply . status ( 204 ) . send ( 'ok' ) ; } , } ) ; function never ( _ ) { throw Error ( 'never' ) ; } type ComplexHandlers = RequestHandler < ExtendedSchema , 'GET /' | 'POST /' | 'PATCH /' | 'PUT /other' | 'PATCH /other' | 'PATCH /other_empty' > ; const complexHandlers = ( req , reply ) : ComplexHandlers [ 'Return' ] => { if ( 'getQueryParam' in reply . request . query ) { reply . request . query . postQueryParam ; reply . request . query . getQueryParam ; reply . status ( 200 ) ; } else if ( 'postQueryParam' in req . query ) { req . query . postQueryParam ; req . query . getQueryParam ; } function testHeaders ( headers ) { if ( 'postHeader2' in headers ) { headers . postHeader2 ; headers . postHeader ; headers . getHeader ; headers . authorization ; } else if ( 'getHeader' in headers ) { headers . getHeader ; headers . authorization ; headers . postHeader ; } else { headers . authorization ; headers . postHeader ; headers . getHeader ; } } testHeaders ( req . headers ) ; testHeaders ( reply . request . headers ) ; switch ( req . method ) { case 'GET' : req . query . getQueryParam ; break ; case 'POST' : req . body . name ; req . query . postQueryParam ; break ; case 'PATCH' : expectType < '/' | '/other' | '/other_empty' > ( req . routerPath ) ; break ; case 'PUT' : expectType < '/' | '/other' > ( req . routerPath ) ; break ; default : never ( req ) ; } switch ( req . routerPath ) { case '/' : expectType < 'GET' | 'PATCH' | 'POST' > ( req . method ) ; expectType < 'GET' | 'PATCH' | 'POST' > ( req . routerMethod ) ; break ; case '/other_empty' : expectType < 'PATCH' > ( req . routerMethod ) ; break ; case '/other' : expectType < 'PUT' | 'PATCH' > ( req . method ) ; expectType < 'PUT' | 'PATCH' > ( req . routerMethod ) ; break ; default : never ( req ) ; } switch ( reply . request . method ) { case 'GET' : reply . request . query . getQueryParam ; reply . request . query . postQueryParam ; break ; case 'POST' : reply . request . body . name ; reply . request . query . postQueryParam ; break ; case 'PUT' : break ; case 'PATCH' : break ; default : never ( reply . request ) ; } const routerPathWithMethod = `${ reply . request . method } ${ reply . request . routerPath }` as ComplexHandlers [ 'Paths' ] ; switch ( routerPathWithMethod ) { case 'GET /' : case 'POST /' : case 'PUT /other' : case 'PATCH /other' : case 'PATCH /other_empty' : case 'PATCH /' : break ; default : never ( routerPathWithMethod ) ; } const logByPath = { 'GET /' : ( ) => console . log ( 1 ) , 'PUT /other' : ( ) => console . log ( 1 ) , 'PATCH /other' : ( ) => console . log ( 1 ) , 'PATCH /other_empty' : ( ) => console . log ( 1 ) , 'POST /' : ( ) => console . log ( 1 ) , 'PATCH /' : ( ) => console . log ( 1 ) , } ; logByPath [ routerPathWithMethod ] ( ) ; const randomNumber = Math . random ( ) ; if ( randomNumber ) { return reply ; } else if ( randomNumber ) { return reply . redirect ( '/' ) ; } else if ( randomNumber ) { return reply . redirect ( 200 , '/' ) . done ( ) ; } if ( reply . matches ( 'GET /' ) ) { reply . request . query . postQueryParam ; reply . request . query . getQueryParam ; reply . code ( 204 ) ; const fastifyReply = reply . status ( 200 ) ; const fastifyReply1 = fastifyReply . headers ( { 'x-custom' : '1' } ) ; return fastifyReply1 . send ( user ) ; } else if ( reply . matches ( 'PUT /NOT_HANDLED_BY_COMPLEX_HANDLER' ) ) { const asReply = reply . done ( ) ; return never ( asReply ) ; } else if ( reply . matches ( 'POST /' ) ) { reply . request . query . getQueryParam ; if ( randomNumber > 0.5 ) { return reply ; } if ( randomNumber > 0.5 ) { return reply . send ( ) ; } const of = reply . status ( 201 ) ; if ( randomNumber > 0.5 ) { return of . done ( ) ; } const replyWithSecondHeader = of . header ( 'postHeaderRes2' , '2' ) ; replyWithSecondHeader . done ( ) ; if ( randomNumber ) { replyWithSecondHeader . headers ( { ... replyWithSecondHeader . getHeaders ( ) , } ) ; replyWithSecondHeader . headers ( { postHeaderRes2 : replyWithSecondHeader . getHeader ( 'postHeaderRes2' ) , postHeaderRes1 : replyWithSecondHeader . getHeader ( 'postHeaderRes1' ) , } ) ; return replyWithSecondHeader . headers ( { ... replyWithSecondHeader . getHeaders ( ) , postHeaderRes : '1' , } ) . send ( user ) ; } return replyWithSecondHeader . header ( 'postHeaderRes2' , '332' ) . header ( 'postHeaderRes' , '3' ) . send ( user ) ; } else if ( reply . matches ( 'PATCH /' ) ) { reply . status ( 204 ) . send ( 1 ) ; return reply . status ( 204 ) . send ( 'ok' ) ; } else if ( reply . matches ( 'PUT /other' ) ) { return reply . status ( 200 ) . send ( { name : '1' } ) ; } else if ( reply . matches ( 'PATCH /other' ) ) { const fastifyReply4 = reply . status ( 204 ) ; fastifyReply4 . send ( void 0 ) ; if ( randomNumber ) { return fastifyReply4 ; } return fastifyReply4 . send ( 'ok' ) ; } else if ( reply . matches ( 'PATCH /other_empty' ) ) { reply . send ( void 0 ) ; return reply . status ( 204 ) . send ( ) ; } else { never ( reply ) ; return reply ; } } ; type TestHandlers = RequestHandler < ExtendedSchema , 'GET /test' | 'POST /test' > ; const testHandler = async ( req , reply ) : TestHandlers [ 'ReturnAsync' ] => { if ( reply . matches ( 'GET /test' ) ) { return reply . status ( 200 ) . send ( 'ok' ) ; } else { return reply . status ( 200 ) . send ( 'ok' ) ; } } ; expectType < Service < ExtendedSchema > > ( { 'GET /' : complexHandlers , 'POST /' : complexHandlers , 'PATCH /' : complexHandlers , 'PUT /other' : complexHandlers , 'PATCH /other' : complexHandlers , 'PATCH /other_empty' : complexHandlers , 'GET /test' : testHandler , 'POST /test' : testHandler , 'PUT /NOT_HANDLED_BY_COMPLEX_HANDLER' : complexHandlers as RequestHandler < ExtendedSchema , 'PUT /NOT_HANDLED_BY_COMPLEX_HANDLER' > [ 'AsRoute' ] , } ) ; interface EmptyResponses extends Schema { paths : { 'GET /empty' : { response : { 204 : { } ; } ; } ; } ; } type AsyncEmptyHandler = RequestHandler < EmptyResponses , 'GET /empty' > ; let handler = ( async ( req , reply ) => { return reply . status ( 204 ) ; } ) as AsyncEmptyHandler [ 'AsRoute' ] ; const handlerSync = ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } ; expectType < Service < EmptyResponses > [ 'GET /empty' ] > ( handler ) ; expectType < Service < EmptyResponses > [ 'GET /empty' ] > ( handlerSync ) ; expectType < AsyncEmptyHandler [ 'AsRoute' ] > ( ( req , reply ) => { return reply . status ( 204 ) ; } ) ; expectType < Service < EmptyResponses > > ( { 'GET /empty' : ( req , reply ) => { return reply . status ( 204 ) ; } , } ) ; expectType < Service < EmptyResponses > > ( { 'GET /empty' : ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , } ) ; expectType < AsyncEmptyHandler [ 'AsRoute' ] > ( ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } ) ; expectType < AsyncEmptyHandler [ 'AsRoute' ] > ( async ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } ) ; expectType < AsyncEmptyHandler [ 'AsRouteObj' ] > ( { preHandler : async ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , handler : async ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , } ) ; expectType < Service < EmptyResponses > > ( { 'GET /empty' : ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , } ) ; expectType < Service < EmptyResponses > > ( { 'GET /empty' : async ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , } ) ; type Created = Service < EmptyResponses > [ 'GET /empty' ] ; expectType < Created > ( async ( req , reply ) => { return reply . status ( 204 ) ; } ) ; const s = { 'GET /empty' : async ( req , reply ) => { return reply . status ( 204 ) ; } , } ; expectType < Service < EmptyResponses > > ( s ) ; expectType < AsyncEmptyHandler [ 'AsRoute' ] > ( async ( req , reply ) => { return reply . status ( 204 ) ; } ) ; expectType < AsyncEmptyHandler [ 'AsRouteObj' ] > ( { handler : async ( req , reply ) => { return reply . status ( 204 ) ; } , } ) ; expectType < AsyncEmptyHandler [ 'AsRouteObj' ] > ( { } ) ; expectType < AsyncEmptyHandler [ 'AsRouteObj' ] > ( { preHandler : async ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , } ) ; expectType < AsyncEmptyHandler [ 'AsRouteObj' ] > ( { preHandler : async ( req , reply ) => { return reply . status ( 204 ) . send ( 111 ) ; } , handler : async ( req , reply ) => { return reply . status ( 204 ) . send ( ) ; } , } ) ; interface Redirects extends Schema { paths : { 'POST /redirect' : { response : { 302 : { } ; } ; } ; } ; } expectType < Service < Redirects > > ( { 'POST /redirect' : ( req , reply ) => { return reply . redirect ( 'example.com' ) ; } , } ) ; interface Params extends Schema { paths : { 'GET /params/:ids/:subid' : { response : { 200 : { } ; } ; } ; 'GET /mixed/:shouldBeNumber/:string' : { request : { params : { shouldBeNumber : number ; } ; } ; response : { 200 : { } ; } ; } ; 'POST /validation/:present/:missing' : { request : { params : { present : string ; } ; } ; response : { 200 : { } ; } ; } ; 'POST /typoInParam/:present' : { request : { params : { presentTypo : string ; } ; } ; response : { 200 : { } ; } ; } ; 'POST /params/:ids/:subid' : { request : { params : { ids : string ; subid : string ; } ; } ; response : { 200 : { } ; } ; } ; } ; } expectType < Service < Params > > ( { 'GET /params/:ids/:subid' : ( req , reply ) => { req . params . subid ; req . params . ids ; req . params . subId ; req . params . wrong ; return reply . status ( 200 ) . send ( ) ; } , 'GET /mixed/:shouldBeNumber/:string' : ( req , reply ) => { expectType < string > ( req . params . string ) ; expectType < number > ( req . params . shouldBeNumber ) ; expectNotType < string > ( req . params . shouldBeNumber ) ; return reply . status ( 200 ) . send ( ) ; } , 'POST /validation/:present/:missing' : ( req , reply ) => { expectType < string > ( req . params . present ) ; expectType < string > ( req . params . missing ) ; return reply . status ( 200 ) . send ( ) ; } , 'POST /typoInParam/:present' : ( req , reply ) => { return reply . status ( 200 ) . send ( ) ; } , 'POST /params/:ids/:subid' : ( req , reply ) => { req . params . subid ; return reply . status ( 200 ) . send ( ) ; } , } ) ;