@fiado/type-kit 3.419.0 → 3.420.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_test_/unit/messagesConnector/IsTemplatePlaceholders.test.ts +43 -0
- package/bin/messagesConnector/validators/IsTemplatePlaceholders.d.ts +5 -4
- package/bin/messagesConnector/validators/IsTemplatePlaceholders.js +22 -29
- package/package.json +1 -1
- package/src/messagesConnector/validators/IsTemplatePlaceholders.ts +21 -24
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { IsTemplatePlaceholders } from '../../../src/messagesConnector/validators/IsTemplatePlaceholders';
|
|
2
|
+
|
|
3
|
+
const validador = new IsTemplatePlaceholders();
|
|
4
|
+
const acepta = (texto: unknown): boolean => validador.validate(texto);
|
|
5
|
+
|
|
6
|
+
describe('IsTemplatePlaceholders', () => {
|
|
7
|
+
describe('acepta las dos formas que el renderizador sustituye', () => {
|
|
8
|
+
it.each([
|
|
9
|
+
['sin placeholders', 'Hola, tu credito esta al corriente'],
|
|
10
|
+
['forma simple', 'Hola {cliente_nombre}, tu cuota es {monto}'],
|
|
11
|
+
['forma doble', 'Hola {{cliente_nombre}}, tu cuota es {{monto}}'],
|
|
12
|
+
['las dos mezcladas', 'Hola {{cliente_nombre}}, vence el {fecha_venc}'],
|
|
13
|
+
['nombre con digitos y guion bajo', 'Codigo {var1} y {otra_var2}'],
|
|
14
|
+
['cadena vacia', ''],
|
|
15
|
+
])('%s', (_caso, texto) => {
|
|
16
|
+
expect(acepta(texto)).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
describe('rechaza lo que el renderizador no podria sustituir', () => {
|
|
21
|
+
it.each([
|
|
22
|
+
['placeholder vacio simple', '{}'],
|
|
23
|
+
['placeholder vacio doble', '{{}}'],
|
|
24
|
+
['nombre con espacio', '{var name}'],
|
|
25
|
+
['nombre con guion', '{var-1}'],
|
|
26
|
+
['nombre que arranca con digito', '{1var}'],
|
|
27
|
+
['apertura sin cierre', 'abc {def'],
|
|
28
|
+
['cierre sin apertura', 'abc } def'],
|
|
29
|
+
['llave suelta antes de uno valido', '{ {anidado}'],
|
|
30
|
+
['doble sin cerrar del todo', '{{a}'],
|
|
31
|
+
['no es string', 42],
|
|
32
|
+
['nulo', null],
|
|
33
|
+
])('%s', (_caso, texto) => {
|
|
34
|
+
expect(acepta(texto)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('el mensaje nombra las dos formas, para que el front sepa cual usar', () => {
|
|
39
|
+
const mensaje = validador.defaultMessage({ property: 'template' } as never);
|
|
40
|
+
expect(mensaje).toContain('{nombre}');
|
|
41
|
+
expect(mensaje).toContain('{{nombre}}');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ValidatorConstraintInterface, ValidationArguments } from 'class-validator';
|
|
2
2
|
/**
|
|
3
|
-
* Valida que un string solo contenga placeholders bien formados
|
|
4
|
-
* `varName`
|
|
3
|
+
* Valida que un string solo contenga placeholders bien formados, en cualquiera de las dos
|
|
4
|
+
* formas del catalogo: `{varName}` o `{{varName}}`, con `varName` matcheando
|
|
5
|
+
* `^[a-zA-Z][a-zA-Z0-9_]*$`. Rechaza:
|
|
5
6
|
* - Placeholders con espacios o caracteres invalidos (`{var name}`, `{var-1}`).
|
|
6
|
-
* - Llaves sin balancear (`abc {def`, `abc } def`, `{ {nested}`
|
|
7
|
-
* - Placeholders vacios (`{}`).
|
|
7
|
+
* - Llaves sin balancear (`abc {def`, `abc } def`, `{ {nested}`).
|
|
8
|
+
* - Placeholders vacios (`{}`, `{{}}`).
|
|
8
9
|
* Acepta texto sin placeholders en absoluto.
|
|
9
10
|
*
|
|
10
11
|
* Uso en DTOs: `@Validate(IsTemplatePlaceholders)` sobre un campo string.
|
|
@@ -10,11 +10,17 @@ exports.IsTemplatePlaceholders = void 0;
|
|
|
10
10
|
const class_validator_1 = require("class-validator");
|
|
11
11
|
const VALID_PLACEHOLDER = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
* `
|
|
13
|
+
* Las DOS formas que el renderizador sustituye: doble `{{monto}}` y simple `{monto}`.
|
|
14
|
+
* La doble va primero para que no gane el `{monto}` interno, igual que alla.
|
|
15
|
+
*/
|
|
16
|
+
const PLACEHOLDER_PATTERN = /\{\{([^{}]*)\}\}|\{([^{}]*)\}/g;
|
|
17
|
+
/**
|
|
18
|
+
* Valida que un string solo contenga placeholders bien formados, en cualquiera de las dos
|
|
19
|
+
* formas del catalogo: `{varName}` o `{{varName}}`, con `varName` matcheando
|
|
20
|
+
* `^[a-zA-Z][a-zA-Z0-9_]*$`. Rechaza:
|
|
15
21
|
* - Placeholders con espacios o caracteres invalidos (`{var name}`, `{var-1}`).
|
|
16
|
-
* - Llaves sin balancear (`abc {def`, `abc } def`, `{ {nested}`
|
|
17
|
-
* - Placeholders vacios (`{}`).
|
|
22
|
+
* - Llaves sin balancear (`abc {def`, `abc } def`, `{ {nested}`).
|
|
23
|
+
* - Placeholders vacios (`{}`, `{{}}`).
|
|
18
24
|
* Acepta texto sin placeholders en absoluto.
|
|
19
25
|
*
|
|
20
26
|
* Uso en DTOs: `@Validate(IsTemplatePlaceholders)` sobre un campo string.
|
|
@@ -23,33 +29,20 @@ let IsTemplatePlaceholders = class IsTemplatePlaceholders {
|
|
|
23
29
|
validate(value) {
|
|
24
30
|
if (typeof value !== 'string')
|
|
25
31
|
return false;
|
|
26
|
-
//
|
|
27
|
-
let
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
depth = 0;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
if (depth !== 0)
|
|
41
|
-
return false; // apertura sin cierre
|
|
42
|
-
// Validar que cada `{...}` matchea el regex.
|
|
43
|
-
const matches = value.match(/\{([^}]*)\}/g) ?? [];
|
|
44
|
-
for (const raw of matches) {
|
|
45
|
-
const inner = raw.slice(1, -1);
|
|
46
|
-
if (!VALID_PLACEHOLDER.test(inner))
|
|
47
|
-
return false;
|
|
48
|
-
}
|
|
49
|
-
return true;
|
|
32
|
+
// Se consumen los placeholders bien formados; lo que sobra no puede tener llaves.
|
|
33
|
+
let wellFormed = true;
|
|
34
|
+
const leftover = value.replace(PLACEHOLDER_PATTERN, (_match, doubleName, singleName) => {
|
|
35
|
+
const name = doubleName ?? singleName ?? '';
|
|
36
|
+
if (!VALID_PLACEHOLDER.test(name))
|
|
37
|
+
wellFormed = false;
|
|
38
|
+
return '';
|
|
39
|
+
});
|
|
40
|
+
if (!wellFormed)
|
|
41
|
+
return false;
|
|
42
|
+
return !leftover.includes('{') && !leftover.includes('}');
|
|
50
43
|
}
|
|
51
44
|
defaultMessage(args) {
|
|
52
|
-
return `${args.property} contiene placeholders mal formados; debe seguir el patron {nombre} con nombre = [a-zA-Z][a-zA-Z0-9_]*`;
|
|
45
|
+
return `${args.property} contiene placeholders mal formados; debe seguir el patron {nombre} o {{nombre}} con nombre = [a-zA-Z][a-zA-Z0-9_]*`;
|
|
53
46
|
}
|
|
54
47
|
};
|
|
55
48
|
exports.IsTemplatePlaceholders = IsTemplatePlaceholders;
|
package/package.json
CHANGED
|
@@ -3,11 +3,18 @@ import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments
|
|
|
3
3
|
const VALID_PLACEHOLDER = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
7
|
-
* `
|
|
6
|
+
* Las DOS formas que el renderizador sustituye: doble `{{monto}}` y simple `{monto}`.
|
|
7
|
+
* La doble va primero para que no gane el `{monto}` interno, igual que alla.
|
|
8
|
+
*/
|
|
9
|
+
const PLACEHOLDER_PATTERN = /\{\{([^{}]*)\}\}|\{([^{}]*)\}/g;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Valida que un string solo contenga placeholders bien formados, en cualquiera de las dos
|
|
13
|
+
* formas del catalogo: `{varName}` o `{{varName}}`, con `varName` matcheando
|
|
14
|
+
* `^[a-zA-Z][a-zA-Z0-9_]*$`. Rechaza:
|
|
8
15
|
* - Placeholders con espacios o caracteres invalidos (`{var name}`, `{var-1}`).
|
|
9
|
-
* - Llaves sin balancear (`abc {def`, `abc } def`, `{ {nested}`
|
|
10
|
-
* - Placeholders vacios (`{}`).
|
|
16
|
+
* - Llaves sin balancear (`abc {def`, `abc } def`, `{ {nested}`).
|
|
17
|
+
* - Placeholders vacios (`{}`, `{{}}`).
|
|
11
18
|
* Acepta texto sin placeholders en absoluto.
|
|
12
19
|
*
|
|
13
20
|
* Uso en DTOs: `@Validate(IsTemplatePlaceholders)` sobre un campo string.
|
|
@@ -17,29 +24,19 @@ export class IsTemplatePlaceholders implements ValidatorConstraintInterface {
|
|
|
17
24
|
validate(value: unknown): boolean {
|
|
18
25
|
if (typeof value !== 'string') return false;
|
|
19
26
|
|
|
20
|
-
//
|
|
21
|
-
let
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
depth = 0;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
if (depth !== 0) return false; // apertura sin cierre
|
|
27
|
+
// Se consumen los placeholders bien formados; lo que sobra no puede tener llaves.
|
|
28
|
+
let wellFormed = true;
|
|
29
|
+
const leftover = value.replace(PLACEHOLDER_PATTERN, (_match, doubleName?: string, singleName?: string) => {
|
|
30
|
+
const name = doubleName ?? singleName ?? '';
|
|
31
|
+
if (!VALID_PLACEHOLDER.test(name)) wellFormed = false;
|
|
32
|
+
return '';
|
|
33
|
+
});
|
|
34
|
+
if (!wellFormed) return false;
|
|
32
35
|
|
|
33
|
-
|
|
34
|
-
const matches = value.match(/\{([^}]*)\}/g) ?? [];
|
|
35
|
-
for (const raw of matches) {
|
|
36
|
-
const inner = raw.slice(1, -1);
|
|
37
|
-
if (!VALID_PLACEHOLDER.test(inner)) return false;
|
|
38
|
-
}
|
|
39
|
-
return true;
|
|
36
|
+
return !leftover.includes('{') && !leftover.includes('}');
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
defaultMessage(args: ValidationArguments): string {
|
|
43
|
-
return `${args.property} contiene placeholders mal formados; debe seguir el patron {nombre} con nombre = [a-zA-Z][a-zA-Z0-9_]*`;
|
|
40
|
+
return `${args.property} contiene placeholders mal formados; debe seguir el patron {nombre} o {{nombre}} con nombre = [a-zA-Z][a-zA-Z0-9_]*`;
|
|
44
41
|
}
|
|
45
42
|
}
|