javascript - Crearea unei validări lanțuite, sigure din punct de vedere al tipului


Sunt în etapa în care trebuie să efectuez mai multe validări în lanț asupra unor date, până acum am făcut următoarele:

// validation.ts

function isData(data: any) {
  return Boolean(data);
}

function isString(data: any) {
  return typeof data === "string";
}

function isLength(data: any, length: number) {
  return data?.length === length;
}

Aș apela apoi aceste funcții una câte una, dar devine puțin obositor. În mod ideal, încerc să creez un API în lanț, așa cum este în exemplul de mai jos, care preia date și le validează (în timp ce returnează tipul corect) cu fiecare funcție din lanț:

validate(data).isData().isString().isLength(3)

Sau ceva de felul acesta. Sunt blocat în a înțelege cum să fac un astfel de API concatenabil, în timp ce trimit date și într-un mod sigur din punct de vedere al tipului către fiecare validator, adică în isString datele ar trebui să nu fie deja undefined.

Răspuns #1

Creați o clasă validator de tip și faceți ca funcțiile să returneze domeniul clasei. Un exemplu proeminent de utilizare este văzut în Modelul Builder.

Link Cod: TypeScript Play

class TypeValidator {
    data: any;
    constructor(data: any) {
        this.data = data;
    }

    isString() {
        if (typeof(this.data) === 'string') {
            return this;
        } else {
            throw Error('[ValueError] Not a string')
        }
    }

    isLength(len: number) {
        if (this.data.length === len) {
            return this;
        } else {
            throw Error('[ValueError] Not of length ' + len)
        }
    }
}

const tv = new TypeValidator('test')
console.log(tv.isString().isLength(4));

Test 1

tv.isString().isLength(3);

VM30:19 Uncaught Error: [ValueError] Not of length 3
    at TypeValidator.isLength (eval at <anonymous> (main-3.js:1239), <anonymous>:19:19)
    at eval (eval at <anonymous> (main-3.js:1239), <anonymous>:24:27)
    at main-3.js:1239

Test 2

const tv = new TypeValidator(['abx'])
tv.isString();

VM31:11 Uncaught Error: [ValueError] Not a string
    at TypeValidator.isString (eval at <anonymous> (main-3.js:1239), <anonymous>:11:19)
    at eval (eval at <anonymous> (main-3.js:1239), <anonymous>:24:16)
    at main-3.js:1239

Răspunsul #2

O abordare diferită este să ai un fel de lanț de validare monadică primitiv, unde fiecare funcție din lanț efectuează o validare și returnează fie o eroare (dacă validarea a eșuat), fie argumentul care a fost transmis (dacă validarea a reușit).

Apoi poți să combini aceste funcții împreună pentru a avea validarea totală.

Rețineți că această implementare simplă distruge informația despre care verificare individuală a eșuat exact; dacă doriți să păstrați asta, trebuie să folosiți monade reale :)

type Validation<A> = "Invalid" | A

function isData<D>(data: D): Validation<D>  {
   return Boolean(data) ? data : "Invalid"
}

function isString<D>(data: D): Validation<D>  {
  return typeof data === "string" ? data : "Invalid"
}

function isLength(data: any, length: number): Validation<{ length: number }>  {
  return data?.length === length ? data : "Invalid"
}

declare function pipe<A, B, C, D>(a: A, f1: (a: A) => B, f2: (b: B) => C, f3: (c: C) => D): D
declare function pipe<A, B, C>(a: A, f1: (a: A) => B, f2: (b: B) => C): C
declare function pipe<A, B>(a: A, f1: (a: A) => B): B

const validate = <D>(d: D) => pipe(
    d,
    isData,
    isString,
    s => isLength(s, 3)
)

Link către terenul de joacă