Services

ValidationService

Servizio statico per la validazione con regole pipe-separated, traduzione errori i18n e registrazione validatori custom.

Panoramica

ValidationService è il motore di validazione di @pzeta/vue-form. Espone metodi statici per validare valori con regole pipe-separated, tradurre le chiavi errore i18n e registrare validatori custom.

Quando usare ValidationService invece di useValidation:

CasoStrumento consigliato
Validazione reattiva di un campo VueuseValidation
Validazione server-side o in un serviceValidationService
Validazione in un test unitarioValidationService
Tradurre errori già ottenutiValidationService.translateErrors()
Registrare un validatore customValidationService.registerValidator()
Validare senza i18n (fallback leggibili)ValidationService.validateWithFallback()

API

import { ValidationService } from '@pzeta/vue-form'

Metodi statici

export class ValidationService {
  // Valida un valore con regole pipe-separated
  static validate(value: unknown, rules: string): ValidationResult

  // Valida, traduce e restituisce tutto in un'operazione
  static validateAndTranslate(
    value: unknown,
    rules: string,
    t: TranslateFunction,
  ): TranslatedValidationResult

  // Valida con messaggi leggibili senza i18n
  static validateWithFallback(value: unknown, rules: string): TranslatedValidationResult

  // Traduce una singola chiave errore
  static translateError(error: string, t: TranslateFunction): string

  // Traduce un array di chiavi errore
  static translateErrors(errors: string[], t: TranslateFunction): string[]

  // Estrae il messaggio fallback da una chiave i18n (senza t())
  static extractFallback(key: string): string

  // Registra un validatore custom
  static registerValidator(name: string, validator: Validator): void

  // Lista tutti i validatori disponibili
  static getAvailableValidators(): string[]
}

Interfacce

export interface ValidationResult {
  isValid: boolean
  errors: string[]  // chiavi i18n (es. "validation.core.required")
}

export interface TranslatedValidationResult extends ValidationResult {
  translatedErrors: string[]  // messaggi tradotti/leggibili
}

export type TranslateFunction = (
  key: string,
  params?: Record<string, string | number>,
) => string

Sintassi regole pipe-separated

Le regole vengono passate come stringa con | come separatore. Vengono eseguite in sequenza; la validazione si ferma al primo errore per regola.

'required|email'
'required|min:8|max:100'
'required|integer|between:1,100'
'required|codicefiscale'

Regole con parametri

FormatoEsempioDescrizione
regolarequiredSenza parametri
regola:valoremin:5Un parametro numerico
regola:val1,val2between:1,100Due parametri
regola:stringapattern:[A-Z]+Parametro stringa

| nei parametri di pattern, regex ed enum

I parametri di pattern, regex ed enum possono contenere |, che resta parte del parametro:

'required|pattern:^(cat|dog)$' // alternanza nella regex
'enum:a|b,c'                   // valori ammessi: "a|b" e "c"

Dopo queste tre regole un segmento che è il nome di un validatore registrato apre comunque una nuova regola (pattern:^(cat|dog)$|required). Dopo ogni altra regola il | apre sempre una nuova regola: i validatori registrati con registerValidator ricevono il parametro fino al | successivo, quindi per un parametro che contiene | si usano pattern, regex o enum. Il parametro di regex e pattern è il pattern senza delimitatori: regex:^[0-9a-f]{6}$, non regex:/^[0-9a-f]{6}$/.

Regole sconosciute

Dalla 2.12.0 un nome di regola non registrato è un validatore sconosciuto in qualsiasi posizione della stringa, non solo in testa:

AmbienteRisultato di validate()
Node con NODE_ENV=development (test, SSR)lancia Error: ValidationService: validatore "<nome>" non trovato
Altrove (browser, build di produzione){ isValid: false, errors: ['validation.core.unknownValidator|name:<nome>'] }, tradotto in Validatore sconosciuto: <nome>
ValidationService.validate('mario@rossi.it', 'required|emial')
// browser / produzione:            { isValid: false, errors: ['validation.core.unknownValidator|name:emial'] }
// Node con NODE_ENV=development:   Error: ValidationService: validatore "emial" non trovato

Fino alla 2.11.x una regola sconosciuta dopo la prima posizione veniva ignorata senza segnalazione: un refuso come required|emial disattivava il controllo dell'email, e lo stesso accadeva con required: true e una validation sconosciuta. Dalla 2.12.0 lo stesso refuso rende il campo sempre non valido, quindi un esempio copiato con un nome inesistente si nota subito.

Nomi diffusi in altre librerie di validazione che @pzeta/vue-form non registra, con l'equivalente:

Non esisteUsa invece
alphanumalphanumeric
same:campo, confirmedregola cross-field match nella prop validationRules di DynamicFormLayouts
acceptedrequired|enum:true: servono entrambe, perché required da solo accetta false ed enum da solo accetta il valore vuoto
date_before:today, date_after:todaybefore:today, after:today
passportpassportnumber
plate:ITvehicleregistration:IT

Per verificare un nome prima di usarlo c'è ValidationService.getAvailableValidators(): 83 validatori built-in, più quelli registrati con registerValidator.

Validatori disponibili (selezione)

RegolaParametriDescrizione
required—Campo obbligatorio
min:nMinimo n caratteri (stringa) o valore n (numero)
max:nMassimo n caratteri o valore
length:nLunghezza esatta
between:min,maxValore compreso tra min e max
email—Formato email valido
url—URL valido
integer—Numero intero
numeric—Valore numerico
positive—Numero positivo
alpha—Solo lettere
alphanumeric—Lettere e numeri
codicefiscale—Codice fiscale italiano
partitaiva—Partita IVA italiana
iban—IBAN valido
password—Password con requisiti standard
strong—Password forte (12+ caratteri, tutti i tipi)
uuid—UUID valido
enum:v1,v2,...Valore tra quelli specificati
regex:patternMatch espressione regolare

Per la lista completa usare ValidationService.getAvailableValidators().

Flusso traduzione errori

validate() restituisce sempre chiavi i18n grezze. Il formato è:

"validation.categoria.chiave"              // senza parametri
"validation.core.min|min:5"               // con un parametro
"validation.core.between|min:1,max:100"   // con più parametri

La parte dopo il | contiene i parametri nel formato nome:valore separati da virgola. translateError() e translateErrors() li estraggono automaticamente e li passano alla funzione t().

Metodi dettagliati

validate()

Valida e restituisce chiavi i18n grezze. Da usare quando si vuole gestire la traduzione separatamente.

const result = ValidationService.validate('test@example.com', 'required|email')
// { isValid: true, errors: [] }

const invalid = ValidationService.validate('', 'required|email')
// { isValid: false, errors: ['validation.core.required'] }

const tooShort = ValidationService.validate('ab', 'required|min:5')
// { isValid: false, errors: ['validation.core.minString|min:5'] }

validateAndTranslate()

Valida e traduce in un'unica operazione. Richiede la funzione t di @pzeta/vue-i18n.

import { useI18n } from '@pzeta/vue-i18n'

const { t } = useI18n()

const result = ValidationService.validateAndTranslate(
  '',
  'required|email',
  t,
)
// {
//   isValid: false,
//   errors: ['validation.core.required'],
//   translatedErrors: ['Questo campo è obbligatorio'],
// }

validateWithFallback()

Valida con messaggi leggibili senza bisogno di t(). Utile in contesti server-side, test o log.

const result = ValidationService.validateWithFallback('', 'required|min:8')
// {
//   isValid: false,
//   errors: ['validation.core.required'],
//   translatedErrors: ['Required'],
// }

translateError() e translateErrors()

Traducono chiavi già ottenute da validate(), estraendo automaticamente i parametri.

const { t } = useI18n()

// Singolo errore con parametro
const msg = ValidationService.translateError('validation.core.minString|min:5', t)
// "Il campo deve contenere almeno 5 caratteri"

// Array di errori
const msgs = ValidationService.translateErrors(
  ['validation.core.required', 'validation.format.email'],
  t,
)
// ['Questo campo è obbligatorio', 'Inserisci un indirizzo email valido']

extractFallback()

Estrae un messaggio di fallback leggibile da una chiave, senza t().

ValidationService.extractFallback('validation.core.required')
// "Required"

ValidationService.extractFallback('validation.core.minString|min:5')
// "Min 5"

registerValidator()

Registra un validatore custom con un nome che può essere usato nelle regole pipe-separated. La forma del Validator è { fn: ValidatorFn, parameterized: boolean } dove fn ritorna un ValidationResult (non true | string).

import { ValidationService } from '@pzeta/vue-form'
import type { Validator } from '@pzeta/vue-form'

// Validatore senza parametri
const codiceBudget: Validator = {
  fn: (value: unknown) => {
    const isValid = typeof value === 'string' && /^BDG-\d{4}-[A-Z]{2}$/.test(value)
    return {
      isValid,
      errors: isValid ? [] : ['validation.custom.codiceBudget'],
    }
  },
  parameterized: false,
}

ValidationService.registerValidator('codiceBudget', codiceBudget)

// Validatore con parametro (sintassi `nome:parametro`)
ValidationService.registerValidator('divisibleBy', {
  fn: (value, divisor) => {
    const num = Number(value)
    const div = Number(divisor)
    const isValid = !Number.isNaN(num) && !Number.isNaN(div) && num % div === 0
    return {
      isValid,
      errors: isValid ? [] : [`validation.custom.divisibleBy|divisor:${divisor ?? ''}`],
    }
  },
  parameterized: true,
})

// Ora disponibili nelle regole
ValidationService.validate('BDG-2024-AB', 'required|codiceBudget')
ValidationService.validate(15, 'divisibleBy:5')
Helper skipEmpty: per validatori opzionali (che devono passare con valori null / undefined / ''), usa l'helper esportato skipEmpty(value) all'inizio di fn. Convenzione: solo required valida i valori vuoti — gli altri validatori li trattano come "saltabili".

getAvailableValidators()

Restituisce la lista di tutti i validatori registrati (built-in + custom).

const validatori = ValidationService.getAvailableValidators()
console.log(validatori)
// ['required', 'min', 'max', 'email', 'url', 'integer', ..., 'codiceBudget']

Esempi completi

Validazione on-submit con traduzione

import { ValidationService } from '@pzeta/vue-form'
import { useI18n } from '@pzeta/vue-i18n'

const { t } = useI18n()

const validateForm = (formData: { email: string; password: string }) => {
  const emailResult = ValidationService.validateAndTranslate(
    formData.email,
    'required|email',
    t,
  )

  const passwordResult = ValidationService.validateAndTranslate(
    formData.password,
    'required|min:8|password',
    t,
  )

  return {
    isValid: emailResult.isValid && passwordResult.isValid,
    errors: {
      email: emailResult.translatedErrors,
      password: passwordResult.translatedErrors,
    },
  }
}

Validazione in un service backend (senza i18n)

import { ValidationService } from '@pzeta/vue-form'

export class AnagraficaService {
  validateInput(data: { codiceFiscale: string; email: string }) {
    const cfResult = ValidationService.validateWithFallback(
      data.codiceFiscale,
      'required|codicefiscale',
    )

    const emailResult = ValidationService.validateWithFallback(
      data.email,
      'required|email',
    )

    if (!cfResult.isValid || !emailResult.isValid) {
      throw new Error([
        ...cfResult.translatedErrors,
        ...emailResult.translatedErrors,
      ].join(', '))
    }
  }
}

Validatore custom con messaggi i18n

Per supportare traduzioni, il validatore deve restituire chiavi i18n registrate nel dizionario dell'app:

// Registrazione validatore (forma { fn, parameterized })
ValidationService.registerValidator('partitaIvaOrCf', {
  fn: (value: unknown) => {
    if (typeof value !== 'string' || value.trim() === '') {
      return { isValid: false, errors: ['validation.core.required'] }
    }
    const cfResult = ValidationService.validate(value, 'codicefiscale')
    const pivaResult = ValidationService.validate(value, 'partitaiva')
    const isValid = cfResult.isValid || pivaResult.isValid
    return {
      isValid,
      errors: isValid ? [] : ['validation.italian.cfOrPiva'],
    }
  },
  parameterized: false,
})

// Uso nelle regole
ValidationService.validate('RSSMRA80A01H501U', 'required|partitaIvaOrCf')
// { isValid: true, errors: [] }

Note

  • I validatori custom vengono aggiunti al registro globale: registrarli una volta al bootstrap dell'app (es. in main.ts).
  • validateWithFallback() usa extractFallback() internamente: il messaggio di fallback è derivato dal nome della chiave, non dal dizionario italiano. Per messaggi precisi usare sempre validateAndTranslate().
  • Il formato dei parametri nelle chiavi errore ("chiave|param:val") è stabile e parte dell'API pubblica.