Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 54a6ed0

Browse filesBrowse files
committed
validate: flatten Plotly.validate output
- make Plotly.validate return a 1-level deep array of error objects - add 'container' and 'trace' key in each error object to locate the error in data trace or layout. - add 'astr' to error object (for easy plug into restyle and relayout)
1 parent a2f2160 commit 54a6ed0
Copy full SHA for 54a6ed0

File tree

Expand file treeCollapse file tree

2 files changed

+320
-91
lines changed
Filter options
Expand file treeCollapse file tree

2 files changed

+320
-91
lines changed

‎src/plot_api/validate.js

Copy file name to clipboardExpand all lines: src/plot_api/validate.js
+172-57Lines changed: 172 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -15,55 +15,75 @@ var Plots = require('../plots/plots');
1515
var PlotSchema = require('./plot_schema');
1616

1717
var isPlainObject = Lib.isPlainObject;
18+
var isArray = Array.isArray;
1819

19-
// validation error codes
20-
var code2msgFunc = {
21-
invisible: function(path) {
22-
return 'trace ' + path + ' got defaulted to be not visible';
23-
},
24-
schema: function(path) {
25-
return 'key ' + path.join('.') + ' is not part of the schema';
26-
},
27-
container: function(path) {
28-
return 'key ' + path.join('.') + ' is supposed to be linked to a container';
29-
},
30-
unused: function(path, valIn) {
31-
var prefix = isPlainObject(valIn) ? 'container' : 'key';
32-
33-
return prefix + ' ' + path.join('.') + ' did not get coerced';
34-
},
35-
value: function(path, valIn) {
36-
return 'key ' + path.join('.') + ' is set to an invalid value (' + valIn + ')';
37-
}
38-
};
3920

21+
/**
22+
* Validate a data array and layout object.
23+
*
24+
* @param {array} data
25+
* @param {object} layout
26+
*
27+
* @return {array} array of error objects each containing:
28+
* - {string} code
29+
* error code ('object', 'array', 'schema', 'unused', 'invisible' or 'value')
30+
* - {string} container
31+
* container where the error occurs ('data' or 'layout')
32+
* - {number} trace
33+
* trace index of the 'data' container where the error occurs
34+
* - {array} path
35+
* nested path to the key that causes the error
36+
* - {string} astr
37+
* attribute string variant of 'path' compatible with Plotly.restyle and
38+
* Plotly.relayout.
39+
* - {string} msg
40+
* error message (shown in console in logger config argument is enable)
41+
*/
4042
module.exports = function valiate(data, layout) {
41-
if(!Array.isArray(data)) {
42-
throw new Error('data must be an array');
43+
var schema = PlotSchema.get(),
44+
errorList = [],
45+
gd = {};
46+
47+
var dataIn, layoutIn;
48+
49+
if(isArray(data)) {
50+
gd.data = Lib.extendDeep([], data);
51+
dataIn = data;
52+
}
53+
else {
54+
gd.data = [];
55+
dataIn = [];
56+
errorList.push(format('array', 'data'));
4357
}
4458

45-
if(!isPlainObject(layout)) {
46-
throw new Error('layout must be an object');
59+
if(isPlainObject(layout)) {
60+
gd.layout = Lib.extendDeep({}, layout);
61+
layoutIn = layout;
62+
}
63+
else {
64+
gd.layout = {};
65+
layoutIn = {};
66+
if(arguments.length > 1) {
67+
errorList.push(format('object', 'layout'));
68+
}
4769
}
4870

49-
var gd = {
50-
data: Lib.extendDeep([], data),
51-
layout: Lib.extendDeep({}, layout)
52-
};
53-
Plots.supplyDefaults(gd);
71+
// N.B. dataIn and layoutIn are in general not the same as
72+
// gd.data and gd.layout after supplyDefaults as some attributes
73+
// in gd.data and gd.layout (still) get mutated during this step.
5474

55-
var schema = PlotSchema.get();
75+
Plots.supplyDefaults(gd);
5676

5777
var dataOut = gd._fullData,
58-
len = data.length,
59-
dataList = new Array(len);
78+
len = dataIn.length;
6079

6180
for(var i = 0; i < len; i++) {
62-
var traceIn = data[i];
63-
var traceList = dataList[i] = [];
81+
var traceIn = dataIn[i],
82+
base = ['data', i];
6483

6584
if(!isPlainObject(traceIn)) {
66-
throw new Error('each data trace must be an object');
85+
errorList.push(format('object', base));
86+
continue;
6787
}
6888

6989
var traceOut = dataOut[i],
@@ -78,25 +98,22 @@ module.exports = function valiate(data, layout) {
7898
};
7999

80100
if(traceOut.visible === false && traceIn.visible !== false) {
81-
traceList.push(format('invisible', i));
101+
errorList.push(format('invisible', base));
82102
}
83103

84-
crawl(traceIn, traceOut, traceSchema, traceList);
104+
crawl(traceIn, traceOut, traceSchema, errorList, base);
85105
}
86106

87107
var layoutOut = gd._fullLayout,
88-
layoutSchema = fillLayoutSchema(schema, dataOut),
89-
layoutList = [];
108+
layoutSchema = fillLayoutSchema(schema, dataOut);
90109

91-
crawl(layout, layoutOut, layoutSchema, layoutList);
110+
crawl(layoutIn, layoutOut, layoutSchema, errorList, 'layout');
92111

93-
return {
94-
data: dataList,
95-
layout: layoutList
96-
};
112+
// return undefined if no validation errors were found
113+
return (errorList.length === 0) ? void(0) : errorList;
97114
};
98115

99-
function crawl(objIn, objOut, schema, list, path) {
116+
function crawl(objIn, objOut, schema, list, base, path) {
100117
path = path || [];
101118

102119
var keys = Object.keys(objIn);
@@ -113,28 +130,34 @@ function crawl(objIn, objOut, schema, list, path) {
113130
var nestedSchema = getNestedSchema(schema, k);
114131

115132
if(!isInSchema(schema, k)) {
116-
list.push(format('schema', p));
133+
list.push(format('schema', base, p));
117134
}
118135
else if(isPlainObject(valIn) && isPlainObject(valOut)) {
119-
crawl(valIn, valOut, nestedSchema, list, p);
136+
crawl(valIn, valOut, nestedSchema, list, base, p);
120137
}
121-
else if(!isPlainObject(valIn) && isPlainObject(valOut)) {
122-
list.push(format('container', p, valIn));
123-
}
124-
else if(nestedSchema.items && Array.isArray(valIn)) {
138+
else if(nestedSchema.items && isArray(valIn)) {
125139
var itemName = k.substr(0, k.length - 1);
126140

127141
for(var j = 0; j < valIn.length; j++) {
128-
p[p.length - 1] = k + '[' + j + ']';
142+
var _nestedSchema = nestedSchema.items[itemName],
143+
_p = p.slice();
129144

130-
crawl(valIn[j], valOut[j], nestedSchema.items[itemName], list, p);
145+
_p.push(j);
146+
147+
crawl(valIn[j], valOut[j], _nestedSchema, list, base, _p);
131148
}
132149
}
150+
else if(!isPlainObject(valIn) && isPlainObject(valOut)) {
151+
list.push(format('object', base, p, valIn));
152+
}
153+
else if(!isArray(valIn) && isArray(valOut) && nestedSchema.valType !== 'info_array') {
154+
list.push(format('array', base, p, valIn));
155+
}
133156
else if(!(k in objOut)) {
134-
list.push(format('unused', p, valIn));
157+
list.push(format('unused', base, p, valIn));
135158
}
136159
else if(!Lib.validate(valIn, nestedSchema)) {
137-
list.push(format('value', p, valIn));
160+
list.push(format('value', base, p, valIn));
138161
}
139162
}
140163

@@ -155,11 +178,82 @@ function fillLayoutSchema(schema, dataOut) {
155178
return schema.layout.layoutAttributes;
156179
}
157180

158-
function format(code, path, valIn) {
181+
// validation error codes
182+
var code2msgFunc = {
183+
object: function(base, astr) {
184+
var prefix;
185+
186+
if(base === 'layout' && astr === '') prefix = 'The layout argument';
187+
else if(base[0] === 'data') {
188+
prefix = 'Trace ' + base[1] + ' in the data argument';
189+
}
190+
else prefix = inBase(base) + 'key ' + astr;
191+
192+
return prefix + ' must be linked to an object container';
193+
},
194+
array: function(base, astr) {
195+
var prefix;
196+
197+
if(base === 'data') prefix = 'The data argument';
198+
else prefix = inBase(base) + 'key ' + astr;
199+
200+
return prefix + ' must be linked to an array container';
201+
},
202+
schema: function(base, astr) {
203+
return inBase(base) + 'key ' + astr + ' is not part of the schema';
204+
},
205+
unused: function(base, astr, valIn) {
206+
var target = isPlainObject(valIn) ? 'container' : 'key';
207+
208+
return inBase(base) + target + ' ' + astr + ' did not get coerced';
209+
},
210+
invisible: function(base) {
211+
return 'Trace ' + base[1] + ' got defaulted to be not visible';
212+
},
213+
value: function(base, astr, valIn) {
214+
return [
215+
inBase(base) + 'key ' + astr,
216+
'is set to an invalid value (' + valIn + ')'
217+
].join(' ');
218+
}
219+
};
220+
221+
function inBase(base) {
222+
if(isArray(base)) return 'In data trace ' + base[1] + ', ';
223+
224+
return 'In ' + base + ', ';
225+
}
226+
227+
function format(code, base, path, valIn) {
228+
path = path || '';
229+
230+
var container, trace;
231+
232+
// container is either 'data' or 'layout
233+
// trace is the trace index if 'data', null otherwise
234+
235+
if(isArray(base)) {
236+
container = base[0];
237+
trace = base[1];
238+
}
239+
else {
240+
container = base;
241+
trace = null;
242+
}
243+
244+
var astr = convertPathToAttributeString(path),
245+
msg = code2msgFunc[code](base, astr, valIn);
246+
247+
// log to console if logger config option is enabled
248+
Lib.log(msg);
249+
159250
return {
160251
code: code,
252+
container: container,
253+
trace: trace,
161254
path: path,
162-
msg: code2msgFunc[code](path, valIn)
255+
astr: astr,
256+
msg: msg
163257
};
164258
}
165259

@@ -192,3 +286,24 @@ function splitKey(key) {
192286
id: id
193287
};
194288
}
289+
290+
function convertPathToAttributeString(path) {
291+
if(!isArray(path)) return String(path);
292+
293+
var astr = '';
294+
295+
for(var i = 0; i < path.length; i++) {
296+
var p = path[i];
297+
298+
if(typeof p === 'number') {
299+
astr = astr.substr(0, astr.length - 1) + '[' + p + ']';
300+
}
301+
else {
302+
astr += p;
303+
}
304+
305+
if(i < path.length - 1) astr += '.';
306+
}
307+
308+
return astr;
309+
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.