pols-validator 1.0.1
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/.vscode/launch.json +22 -0
- package/.vscode/settings.json +3 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +77 -0
- package/dist/rules.d.ts +37 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +422 -0
- package/dist/rulesEngine.d.ts +24 -0
- package/dist/rulesEngine.d.ts.map +1 -0
- package/dist/rulesEngine.js +22 -0
- package/eslint.config.mjs +19 -0
- package/package.json +26 -0
- package/src/index.ts +87 -0
- package/src/rules.ts +427 -0
- package/src/rulesEngine.ts +36 -0
- package/test/validaciones.ts +22 -0
- package/tsconfig.json +20 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "npm run test",
|
|
11
|
+
"skipFiles": [
|
|
12
|
+
"<node_internals>/**"
|
|
13
|
+
],
|
|
14
|
+
"runtimeExecutable": "npm",
|
|
15
|
+
"runtimeArgs": [
|
|
16
|
+
"run",
|
|
17
|
+
"test",
|
|
18
|
+
"test/validaciones.ts"
|
|
19
|
+
]
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Rules } from "./rules";
|
|
2
|
+
import { RulesEngine, RulesParams } from "./rulesEngine";
|
|
3
|
+
export { RulesEngine } from './rulesEngine';
|
|
4
|
+
export type EvaluateResponse<T> = {
|
|
5
|
+
error: false;
|
|
6
|
+
success: true;
|
|
7
|
+
result: T;
|
|
8
|
+
} | {
|
|
9
|
+
error: true;
|
|
10
|
+
success: false;
|
|
11
|
+
messages: string[];
|
|
12
|
+
};
|
|
13
|
+
export declare const validate: <T>(target: unknown, rules: RulesEngine) => EvaluateResponse<T>;
|
|
14
|
+
export declare const rules: (params?: RulesParams) => Rules;
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,SAAS,CAAA;AAC/B,OAAO,EAAE,WAAW,EAAE,WAAW,EAAW,MAAM,eAAe,CAAA;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,MAAM,MAAM,gBAAgB,CAAC,CAAC,IAAI;IACjC,KAAK,EAAE,KAAK,CAAA;IACZ,OAAO,EAAE,IAAI,CAAA;IACb,MAAM,EAAE,CAAC,CAAA;CACT,GAAG;IACH,KAAK,EAAE,IAAI,CAAA;IACX,OAAO,EAAE,KAAK,CAAA;IACd,QAAQ,EAAE,MAAM,EAAE,CAAA;CAClB,CAAA;AAED,eAAO,MAAM,QAAQ,GAAI,CAAC,UAAU,OAAO,SAAS,WAAW,KAAG,gBAAgB,CAAC,CAAC,CAqEnF,CAAA;AAED,eAAO,MAAM,KAAK,YAAa,WAAW,UAAsB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.rules = exports.validate = exports.RulesEngine = void 0;
|
|
4
|
+
const pols_utils_1 = require("pols-utils");
|
|
5
|
+
const rules_1 = require("./rules");
|
|
6
|
+
var rulesEngine_1 = require("./rulesEngine");
|
|
7
|
+
Object.defineProperty(exports, "RulesEngine", { enumerable: true, get: function () { return rulesEngine_1.RulesEngine; } });
|
|
8
|
+
const validate = (target, rules) => {
|
|
9
|
+
const errorMessages = [];
|
|
10
|
+
if (typeof target == 'string')
|
|
11
|
+
target = target.trim();
|
|
12
|
+
const isEmpty = target == null || (typeof target == 'string' && !target);
|
|
13
|
+
const label = rules.label ?? 'Este valor';
|
|
14
|
+
if (rules.required && isEmpty) {
|
|
15
|
+
return {
|
|
16
|
+
error: true,
|
|
17
|
+
success: false,
|
|
18
|
+
messages: [`'${label}' es requerido`]
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
if (isEmpty) {
|
|
22
|
+
return {
|
|
23
|
+
error: false,
|
|
24
|
+
success: true,
|
|
25
|
+
result: rules.default
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
const wrapper = {
|
|
29
|
+
value: pols_utils_1.PUtils.clone(target),
|
|
30
|
+
label
|
|
31
|
+
};
|
|
32
|
+
for (const validationFunction of Object.values(rules.collection)) {
|
|
33
|
+
const result = validationFunction(wrapper);
|
|
34
|
+
if (!result)
|
|
35
|
+
continue;
|
|
36
|
+
if (typeof result == 'string') {
|
|
37
|
+
errorMessages.push(result);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
if (wrapper.value == null || typeof wrapper.value != 'object')
|
|
42
|
+
throw new Error(`El valor no es un objeto para ser validado contra un esquema: ${wrapper.value}`);
|
|
43
|
+
const newResult = {};
|
|
44
|
+
for (const key in result.schema) {
|
|
45
|
+
const rulesInside = result.schema[key];
|
|
46
|
+
const labelIndise = rulesInside.label ?? key;
|
|
47
|
+
rulesInside.label = `${result.prefix ? `${result.prefix} ` : ''}${labelIndise}`;
|
|
48
|
+
newResult[key] = wrapper.value[key];
|
|
49
|
+
const result2 = (0, exports.validate)(newResult[key], rulesInside);
|
|
50
|
+
if (result2.error == true) {
|
|
51
|
+
errorMessages.push(...result2.messages);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
newResult[key] = result2.result;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
wrapper.value = newResult;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (errorMessages.length) {
|
|
61
|
+
return {
|
|
62
|
+
error: true,
|
|
63
|
+
success: false,
|
|
64
|
+
messages: errorMessages
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
return {
|
|
69
|
+
error: false,
|
|
70
|
+
success: true,
|
|
71
|
+
result: wrapper.value
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
exports.validate = validate;
|
|
76
|
+
const rules = (params) => new rules_1.Rules(params);
|
|
77
|
+
exports.rules = rules;
|
package/dist/rules.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { RulesEngine } from "./rulesEngine";
|
|
2
|
+
export declare class Rules extends RulesEngine {
|
|
3
|
+
isAlphanumeric(): this;
|
|
4
|
+
isEmailAddress(): this;
|
|
5
|
+
isDateTime(): this;
|
|
6
|
+
isDate(): this;
|
|
7
|
+
isTime(): this;
|
|
8
|
+
match(pattern: RegExp): this;
|
|
9
|
+
isNumber(): this;
|
|
10
|
+
isInteger(): this;
|
|
11
|
+
isNatural(): this;
|
|
12
|
+
isNaturalNoZero(): this;
|
|
13
|
+
onlyNumbers(): this;
|
|
14
|
+
maxLength(limit: number): this;
|
|
15
|
+
minLength(limit: number): this;
|
|
16
|
+
length(limit: number): this;
|
|
17
|
+
left(limit: number): this;
|
|
18
|
+
isIn(...elements: unknown[]): this;
|
|
19
|
+
isNotIn(...elements: unknown[]): this;
|
|
20
|
+
gt(limit: number): this;
|
|
21
|
+
gte(limit: number): this;
|
|
22
|
+
lt(limit: number): this;
|
|
23
|
+
lte(limit: number): this;
|
|
24
|
+
beforeOrSameAsNow(): this;
|
|
25
|
+
isObject(schema?: Record<string, RulesEngine>, prefix?: string): this;
|
|
26
|
+
isBoolean(): this;
|
|
27
|
+
upper(): this;
|
|
28
|
+
lower(): this;
|
|
29
|
+
decodeURI(): this;
|
|
30
|
+
round(decimals: number): this;
|
|
31
|
+
cleanDoubleSpaces(): this;
|
|
32
|
+
noSpaces(): this;
|
|
33
|
+
replace(search: string | RegExp, replace: string | ((substring: string, ...args: any[]) => string)): this;
|
|
34
|
+
capitalize(): this;
|
|
35
|
+
split(separator: string | RegExp): this;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=rules.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.d.ts","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAW,MAAM,eAAe,CAAA;AAEpD,qBAAa,KAAM,SAAQ,WAAW;IACrC,cAAc;IAWd,cAAc;IAQd,UAAU;IAcV,MAAM;IAMN,MAAM;IA4BN,KAAK,CAAC,OAAO,EAAE,MAAM;IAQrB,QAAQ;IAUR,SAAS;IAQT,SAAS;IAIT,eAAe;IAIf,WAAW;IAQX,SAAS,CAAC,KAAK,EAAE,MAAM;IAgBvB,SAAS,CAAC,KAAK,EAAE,MAAM;IAQvB,MAAM,CAAC,KAAK,EAAE,MAAM;IAQpB,IAAI,CAAC,KAAK,EAAE,MAAM;IA0ElB,IAAI,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE;IAa3B,OAAO,CAAC,GAAG,QAAQ,EAAE,OAAO,EAAE;IAe9B,EAAE,CAAC,KAAK,EAAE,MAAM;IAQhB,GAAG,CAAC,KAAK,EAAE,MAAM;IAQjB,EAAE,CAAC,KAAK,EAAE,MAAM;IAQhB,GAAG,CAAC,KAAK,EAAE,MAAM;IAQjB,iBAAiB;IASjB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM;IAqB9D,SAAS;IAgDT,KAAK;IAQL,KAAK;IAQL,SAAS;IAQT,KAAK,CAAC,QAAQ,EAAE,MAAM;IAStB,iBAAiB;IAQjB,QAAQ;IASR,OAAO,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,MAAM,CAAC;IAMlG,UAAU;IAQV,KAAK,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM;CAKhC"}
|
package/dist/rules.js
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Rules = void 0;
|
|
4
|
+
const pols_utils_1 = require("pols-utils");
|
|
5
|
+
const rulesEngine_1 = require("./rulesEngine");
|
|
6
|
+
class Rules extends rulesEngine_1.RulesEngine {
|
|
7
|
+
isAlphanumeric() {
|
|
8
|
+
this.add(this.isAlphanumeric.name, (wrapper) => {
|
|
9
|
+
if (typeof wrapper.value == 'number') {
|
|
10
|
+
wrapper.value = wrapper.value.toString();
|
|
11
|
+
}
|
|
12
|
+
else if (typeof wrapper.value != 'string') {
|
|
13
|
+
return `'${wrapper.label}' debe ser un alfanumérico`;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
isEmailAddress() {
|
|
19
|
+
this.isAlphanumeric();
|
|
20
|
+
this.add(this.isEmailAddress.name, (wrapper) => {
|
|
21
|
+
if (!wrapper.value.match(/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/))
|
|
22
|
+
return `'${wrapper.label}' debe ser una dirección de correo`;
|
|
23
|
+
});
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
isDateTime() {
|
|
27
|
+
this.add(this.isDateTime.name, (wrapper) => {
|
|
28
|
+
const message = `'${wrapper.label}' tiene un formato de fecha y hora no válido`;
|
|
29
|
+
if (typeof wrapper.value == 'string' || typeof wrapper.value == 'number' || wrapper.value instanceof Date || wrapper.value instanceof pols_utils_1.PDate) {
|
|
30
|
+
const newDate = new pols_utils_1.PDate(wrapper.value);
|
|
31
|
+
if (newDate.isInvalidDate)
|
|
32
|
+
return message;
|
|
33
|
+
wrapper.value = newDate;
|
|
34
|
+
}
|
|
35
|
+
else {
|
|
36
|
+
return message;
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
isDate() {
|
|
42
|
+
return this.isDateTime().add(this.isDate.name, (wrapper) => {
|
|
43
|
+
wrapper.value.clearTime();
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
isTime() {
|
|
47
|
+
this.add(this.isTime.name, (wrapper) => {
|
|
48
|
+
const message = `'${wrapper.label}' contiene un formato de hora no válido`;
|
|
49
|
+
const value = wrapper.value.toString();
|
|
50
|
+
const parts = value.replace(/[.,]/g, ':').replace('m', '').match(/^([0-2]?[0-9])(:?)([0-5]?[0-9]?)(:?)([0-5]?[0-9]?)([ap]?)\.?m?\.?$/);
|
|
51
|
+
if (!parts)
|
|
52
|
+
return message;
|
|
53
|
+
let hours = Number(parts[1]);
|
|
54
|
+
const minutes = Number(parts[3]);
|
|
55
|
+
const seconds = Number(parts[5]);
|
|
56
|
+
const middle = parts[6];
|
|
57
|
+
if (minutes < 60 && seconds < 60) {
|
|
58
|
+
/* Si middle es diferente de vacío, es porque el usuario tiene la intención de especificar una hora del día, caso contrario, está indicando una duración. */
|
|
59
|
+
if (middle) {
|
|
60
|
+
if (hours > 0 && hours <= 12) {
|
|
61
|
+
if (middle === 'p')
|
|
62
|
+
hours += 12;
|
|
63
|
+
wrapper.value = `${pols_utils_1.PUtils.String.padLeft(hours, 2)}:${pols_utils_1.PUtils.String.padLeft(minutes, 2)}:${pols_utils_1.PUtils.String.padLeft(seconds, 2)}`;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
wrapper.value = `${pols_utils_1.PUtils.String.padLeft(hours, 2)}:${pols_utils_1.PUtils.String.padLeft(minutes, 2)}:${pols_utils_1.PUtils.String.padLeft(seconds, 2)}`;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return message;
|
|
73
|
+
});
|
|
74
|
+
return this;
|
|
75
|
+
}
|
|
76
|
+
match(pattern) {
|
|
77
|
+
this.isAlphanumeric();
|
|
78
|
+
this.add(this.match.name, (wrapper) => {
|
|
79
|
+
if (!wrapper.value.match(pattern))
|
|
80
|
+
return `'${wrapper.label}' no cumple con el formato de texto deseado`;
|
|
81
|
+
});
|
|
82
|
+
return this;
|
|
83
|
+
}
|
|
84
|
+
isNumber() {
|
|
85
|
+
this.add(this.isNumber.name, (wrapper) => {
|
|
86
|
+
const message = `'${wrapper.label}' debe ser un número`;
|
|
87
|
+
const value = Number(wrapper.value);
|
|
88
|
+
if (isNaN(value) || value == Infinity)
|
|
89
|
+
return message;
|
|
90
|
+
wrapper.value = value;
|
|
91
|
+
});
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
isInteger() {
|
|
95
|
+
this.isNumber();
|
|
96
|
+
this.add(this.isInteger.name, (wrapper) => {
|
|
97
|
+
if (wrapper.value != Math.floor(wrapper.value))
|
|
98
|
+
return `'${wrapper.label}' debe ser un número entero`;
|
|
99
|
+
});
|
|
100
|
+
return this;
|
|
101
|
+
}
|
|
102
|
+
isNatural() {
|
|
103
|
+
return this.isInteger().gte(0);
|
|
104
|
+
}
|
|
105
|
+
isNaturalNoZero() {
|
|
106
|
+
return this.isInteger().gt(0);
|
|
107
|
+
}
|
|
108
|
+
onlyNumbers() {
|
|
109
|
+
this.isAlphanumeric();
|
|
110
|
+
this.add(this.onlyNumbers.name, (wrapper) => {
|
|
111
|
+
if (!wrapper.value.match(/^[0-9]+$/))
|
|
112
|
+
return `'${wrapper.label}' debe contener sólo números`;
|
|
113
|
+
});
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
maxLength(limit) {
|
|
117
|
+
this.isAlphanumeric();
|
|
118
|
+
this.add(this.maxLength.name, (wrapper) => {
|
|
119
|
+
if (wrapper.value.length > limit)
|
|
120
|
+
return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como máximo`;
|
|
121
|
+
});
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
// maxCount(limit: number) {
|
|
125
|
+
// this.isArray()
|
|
126
|
+
// this.add(this.maxLength.name, (wrapper: Wrapper<unknown[]>) => {
|
|
127
|
+
// if (wrapper.value.length > limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'elemento' : 'elementos'}' como máximo`
|
|
128
|
+
// })
|
|
129
|
+
// return this
|
|
130
|
+
// }
|
|
131
|
+
minLength(limit) {
|
|
132
|
+
this.isAlphanumeric();
|
|
133
|
+
this.add(this.minLength.name, (wrapper) => {
|
|
134
|
+
if (wrapper.value.length < limit)
|
|
135
|
+
return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como mínimo`;
|
|
136
|
+
});
|
|
137
|
+
return this;
|
|
138
|
+
}
|
|
139
|
+
length(limit) {
|
|
140
|
+
this.isAlphanumeric();
|
|
141
|
+
this.add(this.length.name, (wrapper) => {
|
|
142
|
+
if (wrapper.value.length != limit)
|
|
143
|
+
return `'${wrapper.label}' debe contener sólo '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}'`;
|
|
144
|
+
});
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
left(limit) {
|
|
148
|
+
this.isAlphanumeric();
|
|
149
|
+
this.add(this.length.name, (wrapper) => {
|
|
150
|
+
wrapper.value = wrapper.value.substring(0, limit);
|
|
151
|
+
});
|
|
152
|
+
return this;
|
|
153
|
+
}
|
|
154
|
+
// isArray(checkingElements?: (i: number) => RulesEngine) {
|
|
155
|
+
// this.add(this.isArray.name, (wrapper: Wrapper) => {
|
|
156
|
+
// const message = `'${wrapper.label}' debe ser una lista de elementos`
|
|
157
|
+
// if (typeof wrapper.value == 'string') {
|
|
158
|
+
// try {
|
|
159
|
+
// const value = JSON.parse(wrapper.value)
|
|
160
|
+
// if (!(value instanceof Array)) return message
|
|
161
|
+
// wrapper.value = value
|
|
162
|
+
// } catch (err) {
|
|
163
|
+
// return message
|
|
164
|
+
// }
|
|
165
|
+
// } else {
|
|
166
|
+
// if (!(wrapper.value instanceof Array)) return message
|
|
167
|
+
// }
|
|
168
|
+
// if (checkingElements) {
|
|
169
|
+
// const value = wrapper.value as unknown[]
|
|
170
|
+
// const messageErrors: string[] = []
|
|
171
|
+
// const result: unknown[] = []
|
|
172
|
+
// for (const [i, t] of value.entries()) {
|
|
173
|
+
// const p = wrapper.validator({
|
|
174
|
+
// value: t
|
|
175
|
+
// }, {
|
|
176
|
+
// value: {
|
|
177
|
+
// ...checkingElements(i),
|
|
178
|
+
// to: undefined,
|
|
179
|
+
// }
|
|
180
|
+
// })
|
|
181
|
+
// if (p.error == true) {
|
|
182
|
+
// messageErrors.push(...p.messages)
|
|
183
|
+
// } else {
|
|
184
|
+
// result.push(p.result['value'])
|
|
185
|
+
// }
|
|
186
|
+
// }
|
|
187
|
+
// if (messageErrors.length) {
|
|
188
|
+
// return messageErrors
|
|
189
|
+
// } else {
|
|
190
|
+
// wrapper.value = result
|
|
191
|
+
// }
|
|
192
|
+
// }
|
|
193
|
+
// })
|
|
194
|
+
// return this
|
|
195
|
+
// }
|
|
196
|
+
// isArrayOfObjects(checkingElements?: (i: number) => FieldsStructure, prefixString?: (i: number) => string) {
|
|
197
|
+
// this.isArray()
|
|
198
|
+
// this.add(this.isArrayOfObjects.name, (wrapper: Wrapper<unknown[]>) => {
|
|
199
|
+
// const message = `'${wrapper.label}' debe ser una lista de objetos`
|
|
200
|
+
// const messages: string[] = []
|
|
201
|
+
// for (const [i, v] of wrapper.value.entries()) {
|
|
202
|
+
// const result = isObject(v)
|
|
203
|
+
// if (result.error == true) return message
|
|
204
|
+
// wrapper.value[i] = result.result
|
|
205
|
+
// if (checkingElements) {
|
|
206
|
+
// const v = wrapper.validator(wrapper.value[i], checkingElements(i), prefixString?.(i))
|
|
207
|
+
// if (v.error == true) {
|
|
208
|
+
// messages.push(...v.messages)
|
|
209
|
+
// } else {
|
|
210
|
+
// wrapper.value[i] = v.result
|
|
211
|
+
// }
|
|
212
|
+
// }
|
|
213
|
+
// }
|
|
214
|
+
// if (messages.length) return messages
|
|
215
|
+
// })
|
|
216
|
+
// return this
|
|
217
|
+
// }
|
|
218
|
+
isIn(...elements) {
|
|
219
|
+
this.add(this.isIn.name, (wrapper) => {
|
|
220
|
+
if (wrapper.value instanceof Array) {
|
|
221
|
+
for (const v of wrapper.value) {
|
|
222
|
+
if (!elements.includes(v))
|
|
223
|
+
return `'${wrapper.label}' no contiene valores válidos`;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
if (!elements.includes(wrapper.value))
|
|
228
|
+
return `'${wrapper.label}' no tiene un valor válido`;
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
return this;
|
|
232
|
+
}
|
|
233
|
+
isNotIn(...elements) {
|
|
234
|
+
this.add(this.isNotIn.name, (wrapper) => {
|
|
235
|
+
if (elements.includes(wrapper.value))
|
|
236
|
+
return `'${wrapper.label}' no tiene un valor válido`;
|
|
237
|
+
});
|
|
238
|
+
return this;
|
|
239
|
+
}
|
|
240
|
+
// hasElements(checkingElements?: (i: number) => FieldStructure) {
|
|
241
|
+
// this.isArray(checkingElements)
|
|
242
|
+
// this.add(this.hasElements.name, (wrapper: Wrapper) => {
|
|
243
|
+
// if (!(wrapper.value as unknown[]).length) return `'${wrapper.label}' debe ser una lista con al menos un elemento`
|
|
244
|
+
// })
|
|
245
|
+
// return this
|
|
246
|
+
// }
|
|
247
|
+
gt(limit) {
|
|
248
|
+
this.isNumber();
|
|
249
|
+
this.add(this.gt.name, (wrapper) => {
|
|
250
|
+
if (wrapper.value <= limit)
|
|
251
|
+
return `'${wrapper.label}' debe ser mayor a '${limit}'`;
|
|
252
|
+
});
|
|
253
|
+
return this;
|
|
254
|
+
}
|
|
255
|
+
gte(limit) {
|
|
256
|
+
this.isNumber();
|
|
257
|
+
this.add(this.gte.name, (wrapper) => {
|
|
258
|
+
if (wrapper.value < limit)
|
|
259
|
+
return `'${wrapper.label}' debe ser mayor o igual a '${limit}'`;
|
|
260
|
+
});
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
lt(limit) {
|
|
264
|
+
this.isNumber();
|
|
265
|
+
this.add(this.lt.name, (wrapper) => {
|
|
266
|
+
if (wrapper.value >= limit)
|
|
267
|
+
return `'${wrapper.label}' debe ser menor a '${limit}'`;
|
|
268
|
+
});
|
|
269
|
+
return this;
|
|
270
|
+
}
|
|
271
|
+
lte(limit) {
|
|
272
|
+
return this
|
|
273
|
+
.isNumber()
|
|
274
|
+
.add(this.lte.name, (wrapper) => {
|
|
275
|
+
if (wrapper.value > limit)
|
|
276
|
+
return `'${wrapper.label}' debe ser menor o igual a '${limit}'`;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
beforeOrSameAsNow() {
|
|
280
|
+
return this
|
|
281
|
+
.isDateTime()
|
|
282
|
+
.add(this.beforeOrSameAsNow.name, (wrapper) => {
|
|
283
|
+
const now = new pols_utils_1.PDate;
|
|
284
|
+
if (wrapper.value.time > now.time)
|
|
285
|
+
return `'${wrapper.label}' debe ser anterior o igual a 'ahora'`;
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
isObject(schema, prefix) {
|
|
289
|
+
return this.add(this.isObject.name, (wrapper) => {
|
|
290
|
+
const message = `'${wrapper.label}' debe ser un objeto`;
|
|
291
|
+
if (typeof wrapper.value == 'string') {
|
|
292
|
+
try {
|
|
293
|
+
wrapper.value = JSON.parse(wrapper.value);
|
|
294
|
+
}
|
|
295
|
+
catch {
|
|
296
|
+
return message;
|
|
297
|
+
}
|
|
298
|
+
if (pols_utils_1.PUtils.getType(wrapper.value) != 'Object')
|
|
299
|
+
return message;
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
if (pols_utils_1.PUtils.getType(wrapper.value) != 'Object') {
|
|
303
|
+
return message;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return { schema, prefix };
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
isBoolean() {
|
|
310
|
+
this.add(this.isBoolean.name, (wrapper) => {
|
|
311
|
+
const message = `'${wrapper.label}' debe ser de tipo booleano`;
|
|
312
|
+
if (typeof wrapper.value == 'string') {
|
|
313
|
+
const value = wrapper.value.trim().toUpperCase();
|
|
314
|
+
switch (value) {
|
|
315
|
+
case '1':
|
|
316
|
+
case 'S':
|
|
317
|
+
case 'SÍ':
|
|
318
|
+
case 'Y':
|
|
319
|
+
case 'YES':
|
|
320
|
+
wrapper.value = true;
|
|
321
|
+
return;
|
|
322
|
+
case '0':
|
|
323
|
+
case 'N':
|
|
324
|
+
case 'NO':
|
|
325
|
+
wrapper.value = false;
|
|
326
|
+
return;
|
|
327
|
+
default: {
|
|
328
|
+
let json;
|
|
329
|
+
try {
|
|
330
|
+
json = JSON.parse(wrapper.value);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
return message;
|
|
334
|
+
}
|
|
335
|
+
if (typeof json != 'boolean' && !([0, 1]).includes(json))
|
|
336
|
+
return message;
|
|
337
|
+
wrapper.value = json;
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
else if (typeof wrapper.value == 'number') {
|
|
343
|
+
switch (wrapper.value) {
|
|
344
|
+
case 0:
|
|
345
|
+
wrapper.value = false;
|
|
346
|
+
return;
|
|
347
|
+
case 1:
|
|
348
|
+
wrapper.value = true;
|
|
349
|
+
return;
|
|
350
|
+
default:
|
|
351
|
+
return message;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
return typeof wrapper.value == 'boolean' ? null : message;
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return this;
|
|
359
|
+
}
|
|
360
|
+
upper() {
|
|
361
|
+
this.isAlphanumeric();
|
|
362
|
+
this.add(this.upper.name, (wrapper) => {
|
|
363
|
+
wrapper.value = wrapper.value.toUpperCase();
|
|
364
|
+
});
|
|
365
|
+
return this;
|
|
366
|
+
}
|
|
367
|
+
lower() {
|
|
368
|
+
this.isAlphanumeric();
|
|
369
|
+
this.add(this.lower.name, (wrapper) => {
|
|
370
|
+
wrapper.value = wrapper.value.toLowerCase();
|
|
371
|
+
});
|
|
372
|
+
return this;
|
|
373
|
+
}
|
|
374
|
+
decodeURI() {
|
|
375
|
+
this.isAlphanumeric();
|
|
376
|
+
this.add(this.decodeURI.name, (wrapper) => {
|
|
377
|
+
wrapper.value = decodeURI(wrapper.value);
|
|
378
|
+
});
|
|
379
|
+
return this;
|
|
380
|
+
}
|
|
381
|
+
round(decimals) {
|
|
382
|
+
this.isNumber();
|
|
383
|
+
this.add(this.round.name, (wrapper) => {
|
|
384
|
+
wrapper.value = pols_utils_1.PUtils.Number.round(wrapper.value, decimals);
|
|
385
|
+
});
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
/* Reemplaza todos los dobles (o más) espacios juntos por uno simple */
|
|
389
|
+
cleanDoubleSpaces() {
|
|
390
|
+
this.isAlphanumeric();
|
|
391
|
+
this.add(this.cleanDoubleSpaces.name, (wrapper) => {
|
|
392
|
+
wrapper.value = wrapper.value.replace(/\s{2,}/g, ' ');
|
|
393
|
+
});
|
|
394
|
+
return this;
|
|
395
|
+
}
|
|
396
|
+
noSpaces() {
|
|
397
|
+
this.isAlphanumeric();
|
|
398
|
+
this.add(this.noSpaces.name, (wrapper) => {
|
|
399
|
+
if (wrapper.value.match(/\s/))
|
|
400
|
+
return `'${wrapper.label}' no debe contener 'espacios'`;
|
|
401
|
+
});
|
|
402
|
+
return this;
|
|
403
|
+
}
|
|
404
|
+
replace(search, replace) {
|
|
405
|
+
return this.isAlphanumeric().add(this.replace.name, (wrapper) => {
|
|
406
|
+
wrapper.value = wrapper.value.replace(search, replace);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
capitalize() {
|
|
410
|
+
this.isAlphanumeric();
|
|
411
|
+
this.add(this.capitalize.name, (wrapper) => {
|
|
412
|
+
wrapper.value = pols_utils_1.PUtils.String.capitalize(wrapper.value);
|
|
413
|
+
});
|
|
414
|
+
return this;
|
|
415
|
+
}
|
|
416
|
+
split(separator) {
|
|
417
|
+
return this.isAlphanumeric().add(this.split.name, (wrapper) => {
|
|
418
|
+
wrapper.value = wrapper.value.split(separator);
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
exports.Rules = Rules;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type RulesParams = {
|
|
2
|
+
label?: string;
|
|
3
|
+
} & ({
|
|
4
|
+
required?: boolean;
|
|
5
|
+
} | {
|
|
6
|
+
default?: unknown;
|
|
7
|
+
});
|
|
8
|
+
export type ValidationFunction = (wrapper: Wrapper, ...args: unknown[]) => string | void | null | undefined | {
|
|
9
|
+
schema: Record<string, RulesEngine>;
|
|
10
|
+
prefix?: string;
|
|
11
|
+
};
|
|
12
|
+
export type Wrapper<T = unknown> = {
|
|
13
|
+
value: T;
|
|
14
|
+
label: string;
|
|
15
|
+
};
|
|
16
|
+
export declare class RulesEngine {
|
|
17
|
+
constructor(params?: RulesParams);
|
|
18
|
+
label: string;
|
|
19
|
+
required: boolean;
|
|
20
|
+
default: unknown;
|
|
21
|
+
collection: Record<string, ValidationFunction>;
|
|
22
|
+
protected add(name: string, validationFunction: ValidationFunction): this;
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=rulesEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rulesEngine.d.ts","sourceRoot":"","sources":["../src/rulesEngine.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG;IACzB,KAAK,CAAC,EAAE,MAAM,CAAA;CACd,GAAG,CAAC;IACJ,QAAQ,CAAC,EAAE,OAAO,CAAA;CAClB,GAAG;IACH,OAAO,CAAC,EAAE,OAAO,CAAA;CACjB,CAAC,CAAA;AAEF,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,SAAS,GAAG;IAC7G,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;CACf,CAAA;AAED,MAAM,MAAM,OAAO,CAAC,CAAC,GAAG,OAAO,IAAI;IAClC,KAAK,EAAE,CAAC,CAAA;IACR,KAAK,EAAE,MAAM,CAAA;CACb,CAAA;AAED,qBAAa,WAAW;gBACX,MAAM,CAAC,EAAE,WAAW;IAOhC,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,EAAE,OAAO,CAAQ;IACzB,OAAO,EAAE,OAAO,CAAO;IACvB,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAK;IAEnD,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,kBAAkB,EAAE,kBAAkB;CAIlE"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RulesEngine = void 0;
|
|
4
|
+
class RulesEngine {
|
|
5
|
+
constructor(params) {
|
|
6
|
+
if (!params)
|
|
7
|
+
return;
|
|
8
|
+
this.label = params.label;
|
|
9
|
+
this.required = 'required' in params ? params.required : false;
|
|
10
|
+
this.default = 'default' in params ? params.default : null;
|
|
11
|
+
}
|
|
12
|
+
label;
|
|
13
|
+
required = false;
|
|
14
|
+
default = null;
|
|
15
|
+
collection = {};
|
|
16
|
+
add(name, validationFunction) {
|
|
17
|
+
if (!this.collection[name])
|
|
18
|
+
this.collection[name] = validationFunction;
|
|
19
|
+
return this;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
exports.RulesEngine = RulesEngine;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import globals from "globals";
|
|
2
|
+
import pluginJs from "@eslint/js";
|
|
3
|
+
import tseslint from "typescript-eslint";
|
|
4
|
+
|
|
5
|
+
export default [
|
|
6
|
+
{
|
|
7
|
+
files: ["**/*.{ts}"],
|
|
8
|
+
},
|
|
9
|
+
{ languageOptions: { globals: globals.node } },
|
|
10
|
+
pluginJs.configs.recommended,
|
|
11
|
+
...tseslint.configs.recommended,
|
|
12
|
+
{
|
|
13
|
+
rules: {
|
|
14
|
+
"@typescript-eslint/no-unused-vars": "warn",
|
|
15
|
+
"@typescript-eslint/no-explicit-any": false,
|
|
16
|
+
"no-use-before-define": "off"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
];
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pols-validator",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"main": "dist/index.js",
|
|
5
|
+
"module": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "npx tsc",
|
|
9
|
+
"test": "npx ts-node-dev -r tsconfig-paths/register --project tsconfig.json"
|
|
10
|
+
},
|
|
11
|
+
"author": "Jean Paul Sánchez mendoza",
|
|
12
|
+
"license": "ISC",
|
|
13
|
+
"description": "",
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@eslint/js": "^9.13.0",
|
|
16
|
+
"@types/node": "^22.7.9",
|
|
17
|
+
"eslint": "^9.13.0",
|
|
18
|
+
"globals": "^15.11.0",
|
|
19
|
+
"tsconfig-paths": "^4.2.0",
|
|
20
|
+
"typescript": "^5.6.3",
|
|
21
|
+
"typescript-eslint": "^8.11.0"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"pols-utils": "^1.1.2"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { PUtils } from "pols-utils"
|
|
2
|
+
import { Rules } from "./rules"
|
|
3
|
+
import { RulesEngine, RulesParams, Wrapper } from "./rulesEngine"
|
|
4
|
+
export { RulesEngine } from './rulesEngine'
|
|
5
|
+
|
|
6
|
+
export type EvaluateResponse<T> = {
|
|
7
|
+
error: false
|
|
8
|
+
success: true
|
|
9
|
+
result: T
|
|
10
|
+
} | {
|
|
11
|
+
error: true
|
|
12
|
+
success: false
|
|
13
|
+
messages: string[]
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const validate = <T>(target: unknown, rules: RulesEngine): EvaluateResponse<T> => {
|
|
17
|
+
const errorMessages: string[] = []
|
|
18
|
+
|
|
19
|
+
if (typeof target == 'string') target = target.trim()
|
|
20
|
+
|
|
21
|
+
const isEmpty = target == null || (typeof target == 'string' && !target)
|
|
22
|
+
const label = rules.label ?? 'Este valor'
|
|
23
|
+
|
|
24
|
+
if (rules.required && isEmpty) {
|
|
25
|
+
return {
|
|
26
|
+
error: true,
|
|
27
|
+
success: false,
|
|
28
|
+
messages: [`'${label}' es requerido`]
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (isEmpty) {
|
|
33
|
+
return {
|
|
34
|
+
error: false,
|
|
35
|
+
success: true,
|
|
36
|
+
result: rules.default as T
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const wrapper: Wrapper<T> = {
|
|
41
|
+
value: PUtils.clone(target) as T,
|
|
42
|
+
label
|
|
43
|
+
}
|
|
44
|
+
for (const validationFunction of Object.values(rules.collection)) {
|
|
45
|
+
const result = validationFunction(wrapper)
|
|
46
|
+
if (!result) continue
|
|
47
|
+
if (typeof result == 'string') {
|
|
48
|
+
errorMessages.push(result)
|
|
49
|
+
break
|
|
50
|
+
} else {
|
|
51
|
+
if (wrapper.value == null || typeof wrapper.value != 'object') throw new Error(`El valor no es un objeto para ser validado contra un esquema: ${wrapper.value}`)
|
|
52
|
+
|
|
53
|
+
const newResult: Record<string, unknown> = {}
|
|
54
|
+
for (const key in result.schema) {
|
|
55
|
+
const rulesInside = result.schema[key]
|
|
56
|
+
const labelIndise = rulesInside.label ?? key
|
|
57
|
+
rulesInside.label = `${result.prefix ? `${result.prefix} ` : ''}${labelIndise}`
|
|
58
|
+
|
|
59
|
+
newResult[key] = wrapper.value[key]
|
|
60
|
+
|
|
61
|
+
const result2 = validate(newResult[key], rulesInside)
|
|
62
|
+
if (result2.error == true) {
|
|
63
|
+
errorMessages.push(...result2.messages)
|
|
64
|
+
} else {
|
|
65
|
+
newResult[key] = result2.result
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
wrapper.value = newResult as T
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (errorMessages.length) {
|
|
73
|
+
return {
|
|
74
|
+
error: true,
|
|
75
|
+
success: false,
|
|
76
|
+
messages: errorMessages
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
return {
|
|
80
|
+
error: false,
|
|
81
|
+
success: true,
|
|
82
|
+
result: wrapper.value
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export const rules = (params?: RulesParams) => new Rules(params)
|
package/src/rules.ts
ADDED
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import { PDate, PUtils } from "pols-utils"
|
|
2
|
+
import { RulesEngine, Wrapper } from "./rulesEngine"
|
|
3
|
+
|
|
4
|
+
export class Rules extends RulesEngine {
|
|
5
|
+
isAlphanumeric() {
|
|
6
|
+
this.add(this.isAlphanumeric.name, (wrapper: Wrapper) => {
|
|
7
|
+
if (typeof wrapper.value == 'number') {
|
|
8
|
+
wrapper.value = wrapper.value.toString()
|
|
9
|
+
} else if (typeof wrapper.value != 'string') {
|
|
10
|
+
return `'${wrapper.label}' debe ser un alfanumérico`
|
|
11
|
+
}
|
|
12
|
+
})
|
|
13
|
+
return this
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
isEmailAddress() {
|
|
17
|
+
this.isAlphanumeric()
|
|
18
|
+
this.add(this.isEmailAddress.name, (wrapper: Wrapper) => {
|
|
19
|
+
if (!(wrapper.value as string).match(/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/)) return `'${wrapper.label}' debe ser una dirección de correo`
|
|
20
|
+
})
|
|
21
|
+
return this
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
isDateTime() {
|
|
25
|
+
this.add(this.isDateTime.name, (wrapper: Wrapper) => {
|
|
26
|
+
const message = `'${wrapper.label}' tiene un formato de fecha y hora no válido`
|
|
27
|
+
if (typeof wrapper.value == 'string' || typeof wrapper.value == 'number' || wrapper.value instanceof Date || wrapper.value instanceof PDate) {
|
|
28
|
+
const newDate = new PDate(wrapper.value)
|
|
29
|
+
if (newDate.isInvalidDate) return message
|
|
30
|
+
wrapper.value = newDate
|
|
31
|
+
} else {
|
|
32
|
+
return message
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isDate() {
|
|
39
|
+
return this.isDateTime().add(this.isDate.name, (wrapper: Wrapper<PDate>) => {
|
|
40
|
+
wrapper.value.clearTime()
|
|
41
|
+
})
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
isTime() {
|
|
45
|
+
this.add(this.isTime.name, (wrapper: Wrapper) => {
|
|
46
|
+
const message = `'${wrapper.label}' contiene un formato de hora no válido`
|
|
47
|
+
const value = wrapper.value.toString()
|
|
48
|
+
const parts = value.replace(/[.,]/g, ':').replace('m', '').match(/^([0-2]?[0-9])(:?)([0-5]?[0-9]?)(:?)([0-5]?[0-9]?)([ap]?)\.?m?\.?$/)
|
|
49
|
+
if (!parts) return message
|
|
50
|
+
let hours = Number(parts[1])
|
|
51
|
+
const minutes = Number(parts[3])
|
|
52
|
+
const seconds = Number(parts[5])
|
|
53
|
+
const middle = parts[6]
|
|
54
|
+
if (minutes < 60 && seconds < 60) {
|
|
55
|
+
/* Si middle es diferente de vacío, es porque el usuario tiene la intención de especificar una hora del día, caso contrario, está indicando una duración. */
|
|
56
|
+
if (middle) {
|
|
57
|
+
if (hours > 0 && hours <= 12) {
|
|
58
|
+
if (middle === 'p') hours += 12
|
|
59
|
+
wrapper.value = `${PUtils.String.padLeft(hours, 2)}:${PUtils.String.padLeft(minutes, 2)}:${PUtils.String.padLeft(seconds, 2)}`
|
|
60
|
+
return
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
wrapper.value = `${PUtils.String.padLeft(hours, 2)}:${PUtils.String.padLeft(minutes, 2)}:${PUtils.String.padLeft(seconds, 2)}`
|
|
64
|
+
return
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return message
|
|
68
|
+
})
|
|
69
|
+
return this
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
match(pattern: RegExp) {
|
|
73
|
+
this.isAlphanumeric()
|
|
74
|
+
this.add(this.match.name, (wrapper: Wrapper) => {
|
|
75
|
+
if (!(wrapper.value as string).match(pattern)) return `'${wrapper.label}' no cumple con el formato de texto deseado`
|
|
76
|
+
})
|
|
77
|
+
return this
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
isNumber() {
|
|
81
|
+
this.add(this.isNumber.name, (wrapper: Wrapper) => {
|
|
82
|
+
const message = `'${wrapper.label}' debe ser un número`
|
|
83
|
+
const value = Number(wrapper.value)
|
|
84
|
+
if (isNaN(value) || value == Infinity) return message
|
|
85
|
+
wrapper.value = value
|
|
86
|
+
})
|
|
87
|
+
return this
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
isInteger() {
|
|
91
|
+
this.isNumber()
|
|
92
|
+
this.add(this.isInteger.name, (wrapper: Wrapper) => {
|
|
93
|
+
if (wrapper.value != Math.floor(wrapper.value as number)) return `'${wrapper.label}' debe ser un número entero`
|
|
94
|
+
})
|
|
95
|
+
return this
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
isNatural() {
|
|
99
|
+
return this.isInteger().gte(0)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
isNaturalNoZero() {
|
|
103
|
+
return this.isInteger().gt(0)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
onlyNumbers() {
|
|
107
|
+
this.isAlphanumeric()
|
|
108
|
+
this.add(this.onlyNumbers.name, (wrapper: Wrapper) => {
|
|
109
|
+
if (!(wrapper.value as string).match(/^[0-9]+$/)) return `'${wrapper.label}' debe contener sólo números`
|
|
110
|
+
})
|
|
111
|
+
return this
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
maxLength(limit: number) {
|
|
115
|
+
this.isAlphanumeric()
|
|
116
|
+
this.add(this.maxLength.name, (wrapper: Wrapper) => {
|
|
117
|
+
if ((wrapper.value as string).length > limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como máximo`
|
|
118
|
+
})
|
|
119
|
+
return this
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// maxCount(limit: number) {
|
|
123
|
+
// this.isArray()
|
|
124
|
+
// this.add(this.maxLength.name, (wrapper: Wrapper<unknown[]>) => {
|
|
125
|
+
// if (wrapper.value.length > limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'elemento' : 'elementos'}' como máximo`
|
|
126
|
+
// })
|
|
127
|
+
// return this
|
|
128
|
+
// }
|
|
129
|
+
|
|
130
|
+
minLength(limit: number) {
|
|
131
|
+
this.isAlphanumeric()
|
|
132
|
+
this.add(this.minLength.name, (wrapper: Wrapper) => {
|
|
133
|
+
if ((wrapper.value as string).length < limit) return `'${wrapper.label}' debe contener '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}' como mínimo`
|
|
134
|
+
})
|
|
135
|
+
return this
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
length(limit: number) {
|
|
139
|
+
this.isAlphanumeric()
|
|
140
|
+
this.add(this.length.name, (wrapper: Wrapper) => {
|
|
141
|
+
if ((wrapper.value as string).length != limit) return `'${wrapper.label}' debe contener sólo '${limit} ${limit == 1 ? 'caracter' : 'caracteres'}'`
|
|
142
|
+
})
|
|
143
|
+
return this
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
left(limit: number) {
|
|
147
|
+
this.isAlphanumeric()
|
|
148
|
+
this.add(this.length.name, (wrapper: Wrapper) => {
|
|
149
|
+
(wrapper.value as string) = (wrapper.value as string).substring(0, limit)
|
|
150
|
+
})
|
|
151
|
+
return this
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// isArray(checkingElements?: (i: number) => RulesEngine) {
|
|
155
|
+
// this.add(this.isArray.name, (wrapper: Wrapper) => {
|
|
156
|
+
// const message = `'${wrapper.label}' debe ser una lista de elementos`
|
|
157
|
+
// if (typeof wrapper.value == 'string') {
|
|
158
|
+
// try {
|
|
159
|
+
// const value = JSON.parse(wrapper.value)
|
|
160
|
+
// if (!(value instanceof Array)) return message
|
|
161
|
+
// wrapper.value = value
|
|
162
|
+
// } catch (err) {
|
|
163
|
+
// return message
|
|
164
|
+
// }
|
|
165
|
+
// } else {
|
|
166
|
+
// if (!(wrapper.value instanceof Array)) return message
|
|
167
|
+
// }
|
|
168
|
+
// if (checkingElements) {
|
|
169
|
+
// const value = wrapper.value as unknown[]
|
|
170
|
+
// const messageErrors: string[] = []
|
|
171
|
+
// const result: unknown[] = []
|
|
172
|
+
// for (const [i, t] of value.entries()) {
|
|
173
|
+
// const p = wrapper.validator({
|
|
174
|
+
// value: t
|
|
175
|
+
// }, {
|
|
176
|
+
// value: {
|
|
177
|
+
// ...checkingElements(i),
|
|
178
|
+
// to: undefined,
|
|
179
|
+
// }
|
|
180
|
+
// })
|
|
181
|
+
// if (p.error == true) {
|
|
182
|
+
// messageErrors.push(...p.messages)
|
|
183
|
+
// } else {
|
|
184
|
+
// result.push(p.result['value'])
|
|
185
|
+
// }
|
|
186
|
+
// }
|
|
187
|
+
// if (messageErrors.length) {
|
|
188
|
+
// return messageErrors
|
|
189
|
+
// } else {
|
|
190
|
+
// wrapper.value = result
|
|
191
|
+
// }
|
|
192
|
+
// }
|
|
193
|
+
// })
|
|
194
|
+
// return this
|
|
195
|
+
// }
|
|
196
|
+
|
|
197
|
+
// isArrayOfObjects(checkingElements?: (i: number) => FieldsStructure, prefixString?: (i: number) => string) {
|
|
198
|
+
// this.isArray()
|
|
199
|
+
// this.add(this.isArrayOfObjects.name, (wrapper: Wrapper<unknown[]>) => {
|
|
200
|
+
// const message = `'${wrapper.label}' debe ser una lista de objetos`
|
|
201
|
+
// const messages: string[] = []
|
|
202
|
+
// for (const [i, v] of wrapper.value.entries()) {
|
|
203
|
+
// const result = isObject(v)
|
|
204
|
+
// if (result.error == true) return message
|
|
205
|
+
// wrapper.value[i] = result.result
|
|
206
|
+
// if (checkingElements) {
|
|
207
|
+
// const v = wrapper.validator(wrapper.value[i], checkingElements(i), prefixString?.(i))
|
|
208
|
+
// if (v.error == true) {
|
|
209
|
+
// messages.push(...v.messages)
|
|
210
|
+
// } else {
|
|
211
|
+
// wrapper.value[i] = v.result
|
|
212
|
+
// }
|
|
213
|
+
// }
|
|
214
|
+
// }
|
|
215
|
+
// if (messages.length) return messages
|
|
216
|
+
// })
|
|
217
|
+
// return this
|
|
218
|
+
// }
|
|
219
|
+
|
|
220
|
+
isIn(...elements: unknown[]) {
|
|
221
|
+
this.add(this.isIn.name, (wrapper: Wrapper) => {
|
|
222
|
+
if (wrapper.value instanceof Array) {
|
|
223
|
+
for (const v of wrapper.value) {
|
|
224
|
+
if (!elements.includes(v)) return `'${wrapper.label}' no contiene valores válidos`
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
if (!elements.includes(wrapper.value)) return `'${wrapper.label}' no tiene un valor válido`
|
|
228
|
+
}
|
|
229
|
+
})
|
|
230
|
+
return this
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
isNotIn(...elements: unknown[]) {
|
|
234
|
+
this.add(this.isNotIn.name, (wrapper: Wrapper) => {
|
|
235
|
+
if (elements.includes(wrapper.value)) return `'${wrapper.label}' no tiene un valor válido`
|
|
236
|
+
})
|
|
237
|
+
return this
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// hasElements(checkingElements?: (i: number) => FieldStructure) {
|
|
241
|
+
// this.isArray(checkingElements)
|
|
242
|
+
// this.add(this.hasElements.name, (wrapper: Wrapper) => {
|
|
243
|
+
// if (!(wrapper.value as unknown[]).length) return `'${wrapper.label}' debe ser una lista con al menos un elemento`
|
|
244
|
+
// })
|
|
245
|
+
// return this
|
|
246
|
+
// }
|
|
247
|
+
|
|
248
|
+
gt(limit: number) {
|
|
249
|
+
this.isNumber()
|
|
250
|
+
this.add(this.gt.name, (wrapper: Wrapper) => {
|
|
251
|
+
if (wrapper.value as number <= limit) return `'${wrapper.label}' debe ser mayor a '${limit}'`
|
|
252
|
+
})
|
|
253
|
+
return this
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
gte(limit: number) {
|
|
257
|
+
this.isNumber()
|
|
258
|
+
this.add(this.gte.name, (wrapper: Wrapper) => {
|
|
259
|
+
if (wrapper.value as number < limit) return `'${wrapper.label}' debe ser mayor o igual a '${limit}'`
|
|
260
|
+
})
|
|
261
|
+
return this
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
lt(limit: number) {
|
|
265
|
+
this.isNumber()
|
|
266
|
+
this.add(this.lt.name, (wrapper: Wrapper) => {
|
|
267
|
+
if (wrapper.value as number >= limit) return `'${wrapper.label}' debe ser menor a '${limit}'`
|
|
268
|
+
})
|
|
269
|
+
return this
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
lte(limit: number) {
|
|
273
|
+
return this
|
|
274
|
+
.isNumber()
|
|
275
|
+
.add(this.lte.name, (wrapper: Wrapper) => {
|
|
276
|
+
if (wrapper.value as number > limit) return `'${wrapper.label}' debe ser menor o igual a '${limit}'`
|
|
277
|
+
})
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
beforeOrSameAsNow() {
|
|
281
|
+
return this
|
|
282
|
+
.isDateTime()
|
|
283
|
+
.add(this.beforeOrSameAsNow.name, (wrapper: Wrapper<PDate>) => {
|
|
284
|
+
const now = new PDate
|
|
285
|
+
if (wrapper.value.time > now.time) return `'${wrapper.label}' debe ser anterior o igual a 'ahora'`
|
|
286
|
+
})
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
isObject(schema?: Record<string, RulesEngine>, prefix?: string) {
|
|
290
|
+
return this.add(this.isObject.name, (wrapper: Wrapper) => {
|
|
291
|
+
const message = `'${wrapper.label}' debe ser un objeto`
|
|
292
|
+
|
|
293
|
+
if (typeof wrapper.value == 'string') {
|
|
294
|
+
try {
|
|
295
|
+
wrapper.value = JSON.parse(wrapper.value)
|
|
296
|
+
} catch {
|
|
297
|
+
return message
|
|
298
|
+
}
|
|
299
|
+
if (PUtils.getType(wrapper.value) != 'Object') return message
|
|
300
|
+
} else {
|
|
301
|
+
if (PUtils.getType(wrapper.value) != 'Object') {
|
|
302
|
+
return message
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return { schema, prefix }
|
|
307
|
+
})
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
isBoolean() {
|
|
311
|
+
this.add(this.isBoolean.name, (wrapper: Wrapper) => {
|
|
312
|
+
const message = `'${wrapper.label}' debe ser de tipo booleano`
|
|
313
|
+
if (typeof wrapper.value == 'string') {
|
|
314
|
+
const value = wrapper.value.trim().toUpperCase()
|
|
315
|
+
switch (value) {
|
|
316
|
+
case '1':
|
|
317
|
+
case 'S':
|
|
318
|
+
case 'SÍ':
|
|
319
|
+
case 'Y':
|
|
320
|
+
case 'YES':
|
|
321
|
+
wrapper.value = true
|
|
322
|
+
return
|
|
323
|
+
case '0':
|
|
324
|
+
case 'N':
|
|
325
|
+
case 'NO':
|
|
326
|
+
wrapper.value = false
|
|
327
|
+
return
|
|
328
|
+
default: {
|
|
329
|
+
let json
|
|
330
|
+
try {
|
|
331
|
+
json = JSON.parse(wrapper.value)
|
|
332
|
+
} catch {
|
|
333
|
+
return message
|
|
334
|
+
}
|
|
335
|
+
if (typeof json != 'boolean' && !([0, 1]).includes(json)) return message
|
|
336
|
+
wrapper.value = json
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
} else if (typeof wrapper.value == 'number') {
|
|
341
|
+
switch (wrapper.value) {
|
|
342
|
+
case 0:
|
|
343
|
+
wrapper.value = false
|
|
344
|
+
return
|
|
345
|
+
case 1:
|
|
346
|
+
wrapper.value = true
|
|
347
|
+
return
|
|
348
|
+
default:
|
|
349
|
+
return message
|
|
350
|
+
}
|
|
351
|
+
} else {
|
|
352
|
+
return typeof wrapper.value == 'boolean' ? null : message
|
|
353
|
+
}
|
|
354
|
+
})
|
|
355
|
+
return this
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
upper() {
|
|
359
|
+
this.isAlphanumeric()
|
|
360
|
+
this.add(this.upper.name, (wrapper: Wrapper) => {
|
|
361
|
+
wrapper.value = (wrapper.value as string).toUpperCase()
|
|
362
|
+
})
|
|
363
|
+
return this
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
lower() {
|
|
367
|
+
this.isAlphanumeric()
|
|
368
|
+
this.add(this.lower.name, (wrapper: Wrapper) => {
|
|
369
|
+
wrapper.value = (wrapper.value as string).toLowerCase()
|
|
370
|
+
})
|
|
371
|
+
return this
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
decodeURI() {
|
|
375
|
+
this.isAlphanumeric()
|
|
376
|
+
this.add(this.decodeURI.name, (wrapper: Wrapper) => {
|
|
377
|
+
wrapper.value = decodeURI(wrapper.value as string)
|
|
378
|
+
})
|
|
379
|
+
return this
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
round(decimals: number) {
|
|
383
|
+
this.isNumber()
|
|
384
|
+
this.add(this.round.name, (wrapper: Wrapper) => {
|
|
385
|
+
wrapper.value = PUtils.Number.round(wrapper.value as number, decimals)
|
|
386
|
+
})
|
|
387
|
+
return this
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/* Reemplaza todos los dobles (o más) espacios juntos por uno simple */
|
|
391
|
+
cleanDoubleSpaces() {
|
|
392
|
+
this.isAlphanumeric()
|
|
393
|
+
this.add(this.cleanDoubleSpaces.name, (wrapper: Wrapper) => {
|
|
394
|
+
wrapper.value = (wrapper.value as string).replace(/\s{2,}/g, ' ')
|
|
395
|
+
})
|
|
396
|
+
return this
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
noSpaces() {
|
|
400
|
+
this.isAlphanumeric()
|
|
401
|
+
this.add(this.noSpaces.name, (wrapper: Wrapper) => {
|
|
402
|
+
if ((wrapper.value as string).match(/\s/)) return `'${wrapper.label}' no debe contener 'espacios'`
|
|
403
|
+
})
|
|
404
|
+
return this
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
replace(search: string | RegExp, replace: string | ((substring: string, ...args: any[]) => string)) {
|
|
409
|
+
return this.isAlphanumeric().add(this.replace.name, (wrapper: Wrapper) => {
|
|
410
|
+
wrapper.value = (wrapper.value as string).replace(search, replace as any)
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
capitalize() {
|
|
415
|
+
this.isAlphanumeric()
|
|
416
|
+
this.add(this.capitalize.name, (wrapper: Wrapper) => {
|
|
417
|
+
wrapper.value = PUtils.String.capitalize(wrapper.value as string)
|
|
418
|
+
})
|
|
419
|
+
return this
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
split(separator: string | RegExp) {
|
|
423
|
+
return this.isAlphanumeric().add(this.split.name, (wrapper: Wrapper) => {
|
|
424
|
+
wrapper.value = (wrapper.value as any).split(separator)
|
|
425
|
+
})
|
|
426
|
+
}
|
|
427
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type RulesParams = {
|
|
2
|
+
label?: string
|
|
3
|
+
} & ({
|
|
4
|
+
required?: boolean
|
|
5
|
+
} | {
|
|
6
|
+
default?: unknown
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export type ValidationFunction = (wrapper: Wrapper, ...args: unknown[]) => string | void | null | undefined | {
|
|
10
|
+
schema: Record<string, RulesEngine>
|
|
11
|
+
prefix?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type Wrapper<T = unknown> = {
|
|
15
|
+
value: T
|
|
16
|
+
label: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export class RulesEngine {
|
|
20
|
+
constructor(params?: RulesParams) {
|
|
21
|
+
if (!params) return
|
|
22
|
+
this.label = params.label
|
|
23
|
+
this.required = 'required' in params ? params.required : false
|
|
24
|
+
this.default = 'default' in params ? params.default : null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
label: string
|
|
28
|
+
required: boolean = false
|
|
29
|
+
default: unknown = null
|
|
30
|
+
collection: Record<string, ValidationFunction> = {}
|
|
31
|
+
|
|
32
|
+
protected add(name: string, validationFunction: ValidationFunction) {
|
|
33
|
+
if (!this.collection[name]) this.collection[name] = validationFunction
|
|
34
|
+
return this
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { rules, validate } from '../src/index'
|
|
2
|
+
|
|
3
|
+
const original = {
|
|
4
|
+
uno: 'hola',
|
|
5
|
+
dos: 1,
|
|
6
|
+
tres: '67',
|
|
7
|
+
cuatro: {
|
|
8
|
+
aaa: '2024-10-28 23:56:00',
|
|
9
|
+
bbb: 'fgh'
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const resultados = validate(original, rules({ label: 'Mi número' }).isObject({
|
|
14
|
+
uno: rules({ label: 'Uno', required: true }),
|
|
15
|
+
dos: rules().isBoolean(),
|
|
16
|
+
tres: rules({ required: true }).isNaturalNoZero(),
|
|
17
|
+
cuatro: rules({ label: 'Cuatro', default: {} }).isObject({
|
|
18
|
+
aaa: rules().isDateTime()
|
|
19
|
+
})
|
|
20
|
+
}, `Mi número >`))
|
|
21
|
+
|
|
22
|
+
console.log(original, resultados)
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ESNext",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"strict": false,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"outDir": "./dist"
|
|
13
|
+
},
|
|
14
|
+
"include": [
|
|
15
|
+
"src/**/*.ts"
|
|
16
|
+
],
|
|
17
|
+
"exclude": [
|
|
18
|
+
"node_modules"
|
|
19
|
+
]
|
|
20
|
+
}
|