简体   繁体   中英

How to get typescript to understand the use of a conditional type

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM