Repositories

TypeGuards

Type guards runtime per validazione type-safe — narrowing TypeScript su unknown, gestione difensiva errori Axios e validazione struttura oggetti.

Overview

TypeGuards è una classe statica di type predicates che permettono a TypeScript di fare narrowing sicuro dopo validazione runtime. Risolve il problema dell'unknown proveniente da catch (error: unknown) (TS 4.4+) e da deserializzazioni JSON, evitando crash da type assertion unsafe (as).

import { TypeGuards } from '@pzeta/postgrest'

Usata internamente da RepositoryUtils.handleError per validare la struttura ApiError prima di costruire un RestError.

API

isAxiosError(error): error is AxiosError

Verifica che un errore sia un AxiosError controllando la presenza del flag isAxiosError === true.

try {
  await axios.get('/api/users')
} catch (error) {
  if (TypeGuards.isAxiosError(error)) {
    // TypeScript sa che error: AxiosError
    console.log(error.config?.url)
    console.log(error.code)
  }
}

hasHttpResponse(error): error is AxiosError & { response: NonNullable<...> }

Combina isAxiosError con il check error.response !== undefined. Permette di accedere in sicurezza a status e data della response.

if (TypeGuards.hasHttpResponse(error)) {
  // TypeScript: error.response: AxiosResponse (definita)
  const { status, data } = error.response
  console.log(`HTTP ${status}: ${JSON.stringify(data)}`)
}

Utile per distinguere errori HTTP (con response) da errori di rete (timeout, DNS failure).

hasProperty<K>(obj, prop): obj is Record<K, unknown>

Type guard per verificare la presenza di una singola proprietà su un valore unknown:

const data: unknown = JSON.parse(rawJson)

if (TypeGuards.hasProperty(data, 'name')) {
  // TypeScript: data: Record<'name', unknown>
  console.log(data.name) // type unknown — narrowed dopo ulteriore check
}

hasProperties<K>(obj, ...props): obj is Record<K, unknown>

Variante rest-parameter per verificare multiple proprietà contemporaneamente (AND logico):

const apiError: unknown = response.data

if (TypeGuards.hasProperties(apiError, 'code', 'message')) {
  // TypeScript: apiError ha 'code' e 'message'
  console.log(`${apiError.code}: ${apiError.message}`)
}

Pattern usato in handleError per validare lo shape ApiError (code + message obbligatori).

isNonEmptyString(value): value is string

Verifica che un valore sia una stringa non vuota dopo trim():

TypeGuards.isNonEmptyString('hello')   // true
TypeGuards.isNonEmptyString('')        // false
TypeGuards.isNonEmptyString('   ')     // false (solo whitespace)
TypeGuards.isNonEmptyString(null)      // false
TypeGuards.isNonEmptyString(42)        // false

isValidNumber(value): value is number

Verifica che un valore sia un numero finito e non NaN:

TypeGuards.isValidNumber(42)        // true
TypeGuards.isValidNumber(3.14)      // true
TypeGuards.isValidNumber(NaN)       // false
TypeGuards.isValidNumber(Infinity)  // false
TypeGuards.isValidNumber('42')      // false (stringa numerica)

Esempio: error handling difensivo

import { TypeGuards, RestError } from '@pzeta/postgrest'

try {
  const user = await userRepo.read(1)
} catch (error) {
  // 1. RestError già tipizzato dal repository
  if (error instanceof RestError) {
    handleRestError(error)
    return
  }

  // 2. AxiosError raw (caso raro: errore prima di handleError)
  if (TypeGuards.hasHttpResponse(error)) {
    const { status, data } = error.response

    if (TypeGuards.hasProperties(data, 'code', 'message') &&
        TypeGuards.isNonEmptyString(data.code)) {
      logger.error(`HTTP ${status}${data.code}: ${data.message}`)
    } else {
      logger.error(`HTTP ${status} — payload non standard`, data)
    }
    return
  }

  // 3. Errore di rete / generico
  if (error instanceof Error) {
    logger.error('Errore di rete:', error.message)
    return
  }

  // 4. Tipo sconosciuto
  logger.error('Errore non identificato:', error)
}

Esempio: validazione payload deserializzato

import { TypeGuards } from '@pzeta/postgrest'

function parseUserPayload(raw: unknown): User | null {
  if (!TypeGuards.hasProperties(raw, 'idutente', 'nome', 'email')) {
    return null
  }

  if (!TypeGuards.isValidNumber(raw.idutente)) return null
  if (!TypeGuards.isNonEmptyString(raw.nome)) return null
  if (!TypeGuards.isNonEmptyString(raw.email)) return null

  // TypeScript ha narrowed correttamente
  return {
    idutente: raw.idutente,
    nome: raw.nome,
    email: raw.email,
  }
}
Preferire i type guards a data as User evita crash silenziosi quando l'API cambia shape. Il costo runtime è minimo (poche typeof/in checks).
hasProperty e hasProperties controllano solo la presenza della chiave (prop in obj), non il tipo del valore associato (che resta unknown). Combina con isNonEmptyString / isValidNumber per validazione completa.

Cross-reference