UNPKG

5.88 kBJavaScriptView Raw
1'use strict';
2
3const Annotate = require('./annotate');
4const Common = require('./common');
5const Template = require('./template');
6
7
8const internals = {};
9
10
11exports.Report = class {
12
13 constructor(code, value, local, flags, messages, state, prefs) {
14
15 this.code = code;
16 this.flags = flags;
17 this.messages = messages;
18 this.path = state.path;
19 this.prefs = prefs;
20 this.state = state;
21 this.value = value;
22
23 this.message = null;
24 this.template = null;
25
26 this.local = local || {};
27 this.local.label = exports.label(this.flags, this.state, this.prefs, this.messages);
28
29 if (this.value !== undefined &&
30 !this.local.hasOwnProperty('value')) {
31
32 this.local.value = this.value;
33 }
34
35 if (this.path.length) {
36 const key = this.path[this.path.length - 1];
37 if (typeof key !== 'object') {
38 this.local.key = key;
39 }
40 }
41 }
42
43 _setTemplate(template) {
44
45 this.template = template;
46
47 if (!this.flags.label &&
48 this.path.length === 0) {
49
50 const localized = this._template(this.template, 'root');
51 if (localized) {
52 this.local.label = localized;
53 }
54 }
55 }
56
57 toString() {
58
59 if (this.message) {
60 return this.message;
61 }
62
63 const code = this.code;
64
65 if (!this.prefs.errors.render) {
66 return this.code;
67 }
68
69 const template = this._template(this.template) ||
70 this._template(this.prefs.messages) ||
71 this._template(this.messages);
72
73 if (template === undefined) {
74 return `Error code "${code}" is not defined, your custom type is missing the correct messages definition`;
75 }
76
77 // Render and cache result
78
79 this.message = template.render(this.value, this.state, this.prefs, this.local, { errors: this.prefs.errors, messages: [this.prefs.messages, this.messages] });
80 if (!this.prefs.errors.label) {
81 this.message = this.message.replace(/^"" /, '').trim();
82 }
83
84 return this.message;
85 }
86
87 _template(messages, code) {
88
89 return exports.template(this.value, messages, code || this.code, this.state, this.prefs);
90 }
91};
92
93
94exports.path = function (path) {
95
96 let label = '';
97 for (const segment of path) {
98 if (typeof segment === 'object') { // Exclude array single path segment
99 continue;
100 }
101
102 if (typeof segment === 'string') {
103 if (label) {
104 label += '.';
105 }
106
107 label += segment;
108 }
109 else {
110 label += `[${segment}]`;
111 }
112 }
113
114 return label;
115};
116
117
118exports.template = function (value, messages, code, state, prefs) {
119
120 if (!messages) {
121 return;
122 }
123
124 if (Template.isTemplate(messages)) {
125 return code !== 'root' ? messages : null;
126 }
127
128 let lang = prefs.errors.language;
129 if (Common.isResolvable(lang)) {
130 lang = lang.resolve(value, state, prefs);
131 }
132
133 if (lang &&
134 messages[lang] &&
135 messages[lang][code] !== undefined) {
136
137 return messages[lang][code];
138 }
139
140 return messages[code];
141};
142
143
144exports.label = function (flags, state, prefs, messages) {
145
146 if (flags.label) {
147 return flags.label;
148 }
149
150 if (!prefs.errors.label) {
151 return '';
152 }
153
154 let path = state.path;
155 if (prefs.errors.label === 'key' &&
156 state.path.length > 1) {
157
158 path = state.path.slice(-1);
159 }
160
161 const normalized = exports.path(path);
162 if (normalized) {
163 return normalized;
164 }
165
166 return exports.template(null, prefs.messages, 'root', state, prefs) ||
167 messages && exports.template(null, messages, 'root', state, prefs) ||
168 'value';
169};
170
171
172exports.process = function (errors, original, prefs) {
173
174 if (!errors) {
175 return null;
176 }
177
178 const { override, message, details } = exports.details(errors);
179 if (override) {
180 return override;
181 }
182
183 if (prefs.errors.stack) {
184 return new exports.ValidationError(message, details, original);
185 }
186
187 const limit = Error.stackTraceLimit;
188 Error.stackTraceLimit = 0;
189 const validationError = new exports.ValidationError(message, details, original);
190 Error.stackTraceLimit = limit;
191 return validationError;
192};
193
194
195exports.details = function (errors, options = {}) {
196
197 let messages = [];
198 const details = [];
199
200 for (const item of errors) {
201
202 // Override
203
204 if (item instanceof Error) {
205 if (options.override !== false) {
206 return { override: item };
207 }
208
209 const message = item.toString();
210 messages.push(message);
211
212 details.push({
213 message,
214 type: 'override',
215 context: { error: item }
216 });
217
218 continue;
219 }
220
221 // Report
222
223 const message = item.toString();
224 messages.push(message);
225
226 details.push({
227 message,
228 path: item.path.filter((v) => typeof v !== 'object'),
229 type: item.code,
230 context: item.local
231 });
232 }
233
234 if (messages.length > 1) {
235 messages = [...new Set(messages)];
236 }
237
238 return { message: messages.join('. '), details };
239};
240
241
242exports.ValidationError = class extends Error {
243
244 constructor(message, details, original) {
245
246 super(message);
247 this._original = original;
248 this.details = details;
249 }
250
251 static isError(err) {
252
253 return err instanceof exports.ValidationError;
254 }
255};
256
257
258exports.ValidationError.prototype.isJoi = true;
259
260exports.ValidationError.prototype.name = 'ValidationError';
261
262exports.ValidationError.prototype.annotate = Annotate.error;