UNPKG

9.05 kBPlain TextView Raw
1import * as Joi from "@hapi/joi";
2
3import * 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 */
10export 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 */
242export 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 */
252export 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}