@@ -15,55 +15,75 @@ var Plots = require('../plots/plots');
1515var PlotSchema = require ( './plot_schema' ) ;
1616
1717var 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+ */
4042module . 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