I have a request handler for a post endpoint used to validate receipts. The handler that takes one of two types as an incoming payload {location: 'foo' | 'bar', receipt: string | BarReceipt}
{location: 'foo' | 'bar', receipt: string | BarReceipt}
{location: 'foo' | 'bar', receipt: string | BarReceipt}
. If location is foo
then receipt is always string
and visa versa, if location is bar
then receipt is always BarReceipt
. I'm trying to determine the type of the payload based on a param within the payload and then have typescript understand what the type is without needing to cast it, but I cannot quite figure it out.
// types.ts
export interface ValidateTransactionRequest extends Request {
payload: ValidateTransactionPayload
}
export type ExtractLocation<T> = T extends {
payload: { location: Location.foo }
}
? { payload: FooTransactionPayload }
: T extends {
payload: { location: Location.bar }
}
? { payload: BarTransactionPayload }
: never
export const enum Location {
foo = 'foo',
bar = 'bar',
}
export type ValidateTransactionPayload =
| FooTransactionPayload
| BarTransactionPayload
export interface FooTransactionPayload {
location: Location.foo
receipt: string
}
export interface BarTransactionPayload {
location: Location.bar
receipt: BarReceipt
}
export interface BarReceipt {
orderId: string
}
This is the code where I want typescript to understand which type is in use.
// handler.ts
async function handler<T extends ValidateTransactionRequest>(
request: ExtractLocation<T>,
): Promise<VerificationResponse> {
const { location, receipt } = request.payload
if (location === Location.foo) {
validateFooReceipt(receipt) // typescript warning type 'string | BarReceipt' is not assignable to type 'string'.
} else if (location === Location.bar) {
validateBarReceipt(receipt) // typescript warning type 'string | BarReceipt' is not assignable to type 'BarReceipt'.
}
}
function validateFooReceipt(receipt: string) {...}
function validateBarReceipt(receipt: BarReceipt) {...}
I can cast the type of the payload within each branch of the if statements and move on with my life, but I. Need. To. Know.
Sorry about leaving the bar receipts all over the place.
Answering my own question.
It turns out that the destructuring the payload before the type guard is the issue here. See this thread on github https://github.com/microsoft/TypeScript/issues/13403
I put together an example in the typescript playground here .
type ReceiptRequest = FooReceiptRequest | BarReceiptRequest
interface BarReceiptRequest { payload: { location: 'bar', receipt: BarReceipt } }
interface FooReceiptRequest { payload: { location: 'foo', receipt: string } }
interface BarReceipt {
foo: number
}
const handlerWorks = (request: ReceiptRequest) => {
if (request.payload.location === 'foo') {
takesAString(request.payload.receipt)
} else if (request.payload.location === 'bar') {
takesBarReceipt(request.payload.receipt)
}
// type Error when outside of the type guard :)
takesBarReceipt(request.payload.receipt)
}
const handlerNotWorking = (request: ReceiptRequest) => {
const {receipt, location} = request.payload
if (location === 'foo') {
// type error "Argument of type 'string | BarReceipt' is not assignable to parameter of type 'string'."
takesAString(receipt)
} else if (location === 'bar') {
// type error "Argument of type 'string | BarReceipt' is not assignable to parameter of type 'BarReceipt'."
takesBarReceipt(receipt)
}
}
function takesAString(receipt: string) {}
function takesBarReceipt(receipt: BarReceipt) {}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.