@@ -15,55 +15,75 @@ var Plots = require('../plots/plots');
15
15
var PlotSchema = require ( './plot_schema' ) ;
16
16
17
17
var isPlainObject = Lib . isPlainObject ;
18
+ var isArray = Array . isArray ;
18
19
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
- } ;
39
20
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
+ */
40
42
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' ) ) ;
43
57
}
44
58
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
+ }
47
69
}
48
70
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.
54
74
55
- var schema = PlotSchema . get ( ) ;
75
+ Plots . supplyDefaults ( gd ) ;
56
76
57
77
var dataOut = gd . _fullData ,
58
- len = data . length ,
59
- dataList = new Array ( len ) ;
78
+ len = dataIn . length ;
60
79
61
80
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 ] ;
64
83
65
84
if ( ! isPlainObject ( traceIn ) ) {
66
- throw new Error ( 'each data trace must be an object' ) ;
85
+ errorList . push ( format ( 'object' , base ) ) ;
86
+ continue ;
67
87
}
68
88
69
89
var traceOut = dataOut [ i ] ,
@@ -78,25 +98,22 @@ module.exports = function valiate(data, layout) {
78
98
} ;
79
99
80
100
if ( traceOut . visible === false && traceIn . visible !== false ) {
81
- traceList . push ( format ( 'invisible' , i ) ) ;
101
+ errorList . push ( format ( 'invisible' , base ) ) ;
82
102
}
83
103
84
- crawl ( traceIn , traceOut , traceSchema , traceList ) ;
104
+ crawl ( traceIn , traceOut , traceSchema , errorList , base ) ;
85
105
}
86
106
87
107
var layoutOut = gd . _fullLayout ,
88
- layoutSchema = fillLayoutSchema ( schema , dataOut ) ,
89
- layoutList = [ ] ;
108
+ layoutSchema = fillLayoutSchema ( schema , dataOut ) ;
90
109
91
- crawl ( layout , layoutOut , layoutSchema , layoutList ) ;
110
+ crawl ( layoutIn , layoutOut , layoutSchema , errorList , 'layout' ) ;
92
111
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 ;
97
114
} ;
98
115
99
- function crawl ( objIn , objOut , schema , list , path ) {
116
+ function crawl ( objIn , objOut , schema , list , base , path ) {
100
117
path = path || [ ] ;
101
118
102
119
var keys = Object . keys ( objIn ) ;
@@ -113,28 +130,34 @@ function crawl(objIn, objOut, schema, list, path) {
113
130
var nestedSchema = getNestedSchema ( schema , k ) ;
114
131
115
132
if ( ! isInSchema ( schema , k ) ) {
116
- list . push ( format ( 'schema' , p ) ) ;
133
+ list . push ( format ( 'schema' , base , p ) ) ;
117
134
}
118
135
else if ( isPlainObject ( valIn ) && isPlainObject ( valOut ) ) {
119
- crawl ( valIn , valOut , nestedSchema , list , p ) ;
136
+ crawl ( valIn , valOut , nestedSchema , list , base , p ) ;
120
137
}
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 ) ) {
125
139
var itemName = k . substr ( 0 , k . length - 1 ) ;
126
140
127
141
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 ( ) ;
129
144
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 ) ;
131
148
}
132
149
}
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
+ }
133
156
else if ( ! ( k in objOut ) ) {
134
- list . push ( format ( 'unused' , p , valIn ) ) ;
157
+ list . push ( format ( 'unused' , base , p , valIn ) ) ;
135
158
}
136
159
else if ( ! Lib . validate ( valIn , nestedSchema ) ) {
137
- list . push ( format ( 'value' , p , valIn ) ) ;
160
+ list . push ( format ( 'value' , base , p , valIn ) ) ;
138
161
}
139
162
}
140
163
@@ -155,11 +178,82 @@ function fillLayoutSchema(schema, dataOut) {
155
178
return schema . layout . layoutAttributes ;
156
179
}
157
180
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
+
159
250
return {
160
251
code : code ,
252
+ container : container ,
253
+ trace : trace ,
161
254
path : path ,
162
- msg : code2msgFunc [ code ] ( path , valIn )
255
+ astr : astr ,
256
+ msg : msg
163
257
} ;
164
258
}
165
259
@@ -192,3 +286,24 @@ function splitKey(key) {
192
286
id : id
193
287
} ;
194
288
}
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