1 | import * as Joi from "@hapi/joi";
|
2 |
|
3 | import * as itErrors from "./locale/joi_it";
|
4 |
|
5 | /**
|
6 | * SchemaBuilder for the Joi validator.
|
7 | * It exposes some facility methods for some simple task,
|
8 | * and it also allows a complete personalization of the final result.
|
9 | */
|
10 | export class SchemaBuilder {
|
11 | keys: any;
|
12 | lastKey: string;
|
13 | constructor() {
|
14 | this.keys = {};
|
15 | }
|
16 |
|
17 | /**
|
18 | * Generate the final Joi Object Schema
|
19 | */
|
20 | build(): Joi.ObjectSchema {
|
21 | return Joi.object().keys(this.keys);
|
22 | }
|
23 |
|
24 | /**
|
25 | * General method, that can be used with the Joi functions
|
26 | */
|
27 | general(key: string, spec: any): SchemaBuilder {
|
28 | this.keys[key] = spec;
|
29 | this.lastKey = key;
|
30 | return this;
|
31 | }
|
32 |
|
33 | /**
|
34 | * Add an optional string to the Schema
|
35 | * @param key the key
|
36 | * @param min minimum length of the string
|
37 | * @param max maximum length of the string
|
38 | * @return the schema builder
|
39 | */
|
40 | stringOptional(key: string, min?: number, max?: number): SchemaBuilder {
|
41 | let tmp = Joi.string();
|
42 | if (min !== undefined) {
|
43 | tmp = tmp.min(min);
|
44 | } else {
|
45 | tmp = tmp.allow("");
|
46 | }
|
47 | if (max !== undefined) {
|
48 | tmp = tmp.max(max);
|
49 | }
|
50 | this.keys[key] = tmp;
|
51 | this.lastKey = key;
|
52 | return this;
|
53 | }
|
54 |
|
55 | /**
|
56 | * Add a required string to the Schema
|
57 | * @param key the key
|
58 | * @param min minimum length of the string
|
59 | * @param max maximum length of the string
|
60 | * @return the schema builder
|
61 | */
|
62 | string(key: string, min?: number, max?: number): SchemaBuilder {
|
63 | this.stringOptional(key, min, max);
|
64 | this.keys[key] = this.keys[key].required();
|
65 | this.lastKey = key;
|
66 | return this;
|
67 | }
|
68 |
|
69 | /**
|
70 | * Add a required email to the Schema
|
71 | * @param key the key
|
72 | * @return the schema builder
|
73 | */
|
74 | email(key: string): SchemaBuilder {
|
75 | this.keys[key] = Joi.string()
|
76 | .email()
|
77 | .required();
|
78 | this.lastKey = key;
|
79 | return this;
|
80 | }
|
81 |
|
82 | /**
|
83 | * Add an optional email to the Schema
|
84 | * @param key the key
|
85 | * @return the schema builder
|
86 | */
|
87 | emailOptional(key: string): SchemaBuilder {
|
88 | this.keys[key] = Joi.string().email();
|
89 | this.lastKey = key;
|
90 | return this;
|
91 | }
|
92 |
|
93 | /**
|
94 | * Add a required password to the Schema.
|
95 | * The password will be validated using the following regex:
|
96 | * ^[0-9a-zA-Z\|\!\"\£\$\%\&\/\(\)\=\?\^\,\;\.\:\-\_\\\~]{6,}$
|
97 | * @param key the key
|
98 | * @return the schema builder
|
99 | */
|
100 | password(key: string): SchemaBuilder {
|
101 | this.keys[key] = Joi.string()
|
102 | .regex(
|
103 | /^[0-9a-zA-Z\|\!\"\£\$\%\&\/\(\)\=\?\^\,\;\.\:\-\_\\\~]{6,}$/
|
104 | )
|
105 | .required();
|
106 | return this;
|
107 | }
|
108 |
|
109 | /**
|
110 | * Add an optional password to the Schema.
|
111 | * The password will be validated using the following regex:
|
112 | * ^[0-9a-zA-Z\|\!\"\£\$\%\&\/\(\)\=\?\^\,\;\.\:\-\_\\\~]{6,}$
|
113 | * @param key the key
|
114 | * @return the schema builder
|
115 | */
|
116 | passwordOptional(key: string): SchemaBuilder {
|
117 | this.keys[key] = Joi.string().regex(
|
118 | /^[0-9a-zA-Z\|\!\"\£\$\%\&\/\(\)\=\?\^\,\;\.\:\-\_\\\~]{6,}$/
|
119 | );
|
120 | return this;
|
121 | }
|
122 |
|
123 | /**
|
124 | * Add an optional number to the Schema
|
125 | * @param key the key
|
126 | * @param min minimum value of the number
|
127 | * @param max maximum value of the number
|
128 | * @return the schema builder
|
129 | */
|
130 | numberOptional(key: string, min?: number, max?: number): SchemaBuilder {
|
131 | let tmp = Joi.number();
|
132 | if (min !== undefined) {
|
133 | tmp = tmp.min(min);
|
134 | }
|
135 | if (max !== undefined) {
|
136 | tmp = tmp.max(max);
|
137 | }
|
138 | this.keys[key] = tmp;
|
139 | this.lastKey = key;
|
140 | return this;
|
141 | }
|
142 |
|
143 | /**
|
144 | * Add a required number to the Schema
|
145 | * @param key the key
|
146 | * @param min minimum value of the number
|
147 | * @param max maximum value of the number
|
148 | * @return the schema builder
|
149 | */
|
150 | number(key: string, min?: number, max?: number): SchemaBuilder {
|
151 | this.numberOptional(key, min, max);
|
152 | this.keys[key] = this.keys[key].required();
|
153 | this.lastKey = key;
|
154 | return this;
|
155 | }
|
156 |
|
157 | /**
|
158 | * Add an optional integer number to the Schema
|
159 | * @param key the key
|
160 | * @param min minimum value of the number
|
161 | * @param max maximum value of the number
|
162 | * @return the schema builder
|
163 | */
|
164 | integerOptional(key: string, min?: number, max?: number): SchemaBuilder {
|
165 | let tmp = Joi.number().integer();
|
166 | if (min !== undefined) {
|
167 | tmp = tmp.min(min);
|
168 | }
|
169 | if (max !== undefined) {
|
170 | tmp = tmp.max(max);
|
171 | }
|
172 | this.keys[key] = tmp;
|
173 | this.lastKey = key;
|
174 | return this;
|
175 | }
|
176 |
|
177 | /**
|
178 | * Add a required integer number to the Schema
|
179 | * @param key the key
|
180 | * @param min minimum value of the number
|
181 | * @param max maximum value of the number
|
182 | * @return the schema builder
|
183 | */
|
184 | integer(key: string, min?: number, max?: number): SchemaBuilder {
|
185 | this.integerOptional(key, min, max);
|
186 | this.keys[key] = this.keys[key].required();
|
187 | this.lastKey = key;
|
188 | return this;
|
189 | }
|
190 |
|
191 | /**
|
192 | * Add an optional date to the Schema
|
193 | * @param key the key
|
194 | * @return the schema builder
|
195 | */
|
196 | dateOptional(key: string): SchemaBuilder {
|
197 | this.keys[key] = Joi.date();
|
198 | this.lastKey = key;
|
199 | return this;
|
200 | }
|
201 |
|
202 | /**
|
203 | * Add a required date to the Schema
|
204 | * @param key the key
|
205 | * @return the schema builder
|
206 | */
|
207 | date(key: string): SchemaBuilder {
|
208 | this.keys[key] = Joi.date().required();
|
209 | this.lastKey = key;
|
210 | return this;
|
211 | }
|
212 |
|
213 | arrayOfStrings(key: string): SchemaBuilder {
|
214 | this.keys[key] = Joi.array().items(Joi.string());
|
215 | this.lastKey = key;
|
216 | return this;
|
217 | }
|
218 |
|
219 | arrayOfStringsOptional(key: string): SchemaBuilder {
|
220 | this.keys[key] = Joi.array().items(Joi.string());
|
221 | this.lastKey = key;
|
222 | return this;
|
223 | }
|
224 |
|
225 |
|
226 | /**
|
227 | * Add the label to the last added key
|
228 | * @param label the label to use in case of error
|
229 | * @return the schema builder
|
230 | */
|
231 | withLabel(label: string): SchemaBuilder {
|
232 | if (this.lastKey) {
|
233 | this.keys[this.lastKey] = this.keys[this.lastKey].label(label);
|
234 | }
|
235 | return this;
|
236 | }
|
237 | }
|
238 |
|
239 | /**
|
240 | * Contains the name of the error and a localized error message.
|
241 | */
|
242 | export interface ValidationError {
|
243 | name: string;
|
244 | message: string;
|
245 | }
|
246 |
|
247 | /**
|
248 | * This class is used to validate an object using a given schema.
|
249 | * It is used by Lynx to automatically validate the body of any requests, using
|
250 | * the Body decorator.
|
251 | */
|
252 | export class ValidateObject<T> {
|
253 | private _obj: T;
|
254 | private schema: Joi.Schema;
|
255 | private valid: Joi.ValidationResult;
|
256 |
|
257 | /**
|
258 | * @param obj the object to validate
|
259 | * @param schema the schema
|
260 | * @param locales an array of available language. You can use the `req.acceptsLanguages()`
|
261 | */
|
262 | constructor(obj: any, schema: Joi.Schema, locales: string[]) {
|
263 | this._obj = obj;
|
264 | this.schema = schema;
|
265 | let options = null;
|
266 | for (let locale of locales) {
|
267 | if (locale.indexOf("en") != -1) {
|
268 | break;
|
269 | }
|
270 | if (locale == "it") {
|
271 | options = {
|
272 | language: itErrors.errors
|
273 | };
|
274 | break;
|
275 | }
|
276 | }
|
277 | this.validate(options);
|
278 | }
|
279 |
|
280 | private validate(options: any) {
|
281 | this.valid = this.schema.validate(this._obj, options);
|
282 | }
|
283 |
|
284 | /**
|
285 | * Verify that the object respect the schema.
|
286 | * @return true if the object is valid, false otherwise.
|
287 | */
|
288 | get isValid() {
|
289 | return !this.valid.error;
|
290 | }
|
291 |
|
292 | /**
|
293 | * Unwrap the object (can be valid or not!)
|
294 | * @return the unwrapped object
|
295 | */
|
296 | get obj() {
|
297 | return this._obj;
|
298 | }
|
299 |
|
300 | /**
|
301 | * Getter that returns an array of validation errors.
|
302 | * @return an array of validation errors. It can not be null.
|
303 | */
|
304 | get errors(): ValidationError[] {
|
305 | let errors: ValidationError[] = [];
|
306 | if (this.isValid) {
|
307 | return errors;
|
308 | }
|
309 | if (this.valid.error) {
|
310 | for (let err of this.valid.error!!.details) {
|
311 | errors.push({
|
312 | name: err.context && err.context.key ? err.context.key : "",
|
313 | message: err.message
|
314 | });
|
315 | }
|
316 | }
|
317 | return errors;
|
318 | }
|
319 |
|
320 | /**
|
321 | * Getter that returns a map of errors. This prop contains the save information
|
322 | * as the `errors` prop, but with a different format.
|
323 | * @return a map or localized errors.
|
324 | */
|
325 | get errorsMap(): any {
|
326 | let map: any = {};
|
327 | for (let err of this.errors) {
|
328 | map[err.name] = err.message;
|
329 | }
|
330 | return map;
|
331 | }
|
332 | }
|