One way to model this kind of logic is to use a union type, something like this
interface Valid {
isValid: true
}
interface Invalid {
isValid: false
errorText: string
}
type ValidationResult = Valid | Invalid
const validate = (n: number): ValidationResult => {
return n === 4 ? { isValid: true } : { isValid: false, errorText: "num is not 4" }
}
The compiler is then able to narrow the type down based on the boolean flag
const getErrorTextIfPresent = (r: ValidationResult): string | null => {
return r.isValid ? null : r.errorText
}