@@ -24,11 +24,12 @@ class QCConfigurationAdapter {
2424 * Restrictions for an array always has the following structure
2525 * At index 0 there are Restrictions for every value currently in the array
2626 * At index 1 there are Restrictions in case the user adds a new object to the array
27+ * At index 2 there are Restrictions in case user creates new, directly nested array
2728 *
2829 * For example, for array like:
2930 * [
3031 * { name: 'flp1', strength: 10, isActive: true },
31- * { name: 'flp2', strength: 100000, isActive: 'inactive' }
32+ * { name: 'flp2', strength: 100000, isActive: 'inactive' },
3233 * ]
3334 *
3435 * The ArrayRestrictions will be:
@@ -37,11 +38,15 @@ class QCConfigurationAdapter {
3738 * { name: 'string', strength: 'number', isActive: 'boolean' },
3839 * { name: 'string', strength: 'number', isActive: 'string' }
3940 * ],
40- * { name: 'string', strength: 'number' }
41+ * { name: 'string', strength: 'number' },
42+ * null // because input array does not contain nested arrays
4143 * ]
44+ *
45+ * To see an example of calculating the Restrictions at index 2, see tests
46+ * because writing it in here would bloat the example
4247 */
4348 static get emptyArrayRestrictions ( ) {
44- return [ [ ] , { } ] ;
49+ return [ [ ] , null , null ] ;
4550 }
4651
4752 /**
@@ -81,9 +86,10 @@ class QCConfigurationAdapter {
8186
8287 /**
8388 * Derive the type of values in an array
84- * When deriving restrictions for an array, we gather two pieces of data:
89+ * When deriving restrictions for an array, we gather three pieces of data:
8590 * - itemsRestrictions: a list of types for every object held inside
86- * - newItemRestrictions: a blueprint for a new item in the array
91+ * - newObjectRestrictions: a blueprint for a new item in the array
92+ * - newArrayRestrictions: a blueprints to pass into directly nested newly created array
8793 * @param {Array } array for which we calculate the Restrictions
8894 * @returns {ArrayRestrictions } value describing the nature of objects held in an array
8995 */
@@ -92,23 +98,55 @@ class QCConfigurationAdapter {
9298 return QCConfigurationAdapter . emptyArrayRestrictions ;
9399 }
94100
95- let maximumIntersection = QCConfigurationAdapter . deriveValueType ( array [ 0 ] ) ;
96- const itemsRestrictions = [ maximumIntersection ] ;
101+ const firstItemRestrictions = QCConfigurationAdapter . deriveValueType ( array [ 0 ] ) ;
102+ let innerObjectBlueprint = QCConfigurationAdapter . isObjectOnly ( array [ 0 ] ) ?
103+ firstItemRestrictions : null ;
104+ let [ innerArrayObjectBlueprint , innerArrayArrayBlueprint ] = Array . isArray ( array [ 0 ] ) ?
105+ [ firstItemRestrictions [ 1 ] , firstItemRestrictions [ 2 ] ] : null ;
106+ const itemsRestrictions = [ firstItemRestrictions ] ;
107+
97108 for ( let i = 1 ; i < array . length ; i ++ ) {
98109 const currentItemRestrictions = QCConfigurationAdapter . deriveValueType ( array [ i ] ) ;
99110 itemsRestrictions . push ( currentItemRestrictions ) ;
100- maximumIntersection = QCConfigurationAdapter . getRestrictionIntersection (
101- maximumIntersection ,
102- currentItemRestrictions
103- ) ;
104- }
105111
106- if ( Array . isArray ( maximumIntersection ) ) {
107- // we only want to save the blueprint of a nested array as a blueprint
108- maximumIntersection = [ [ ] , maximumIntersection [ 1 ] ] ;
112+ if ( QCConfigurationAdapter . isPrimitive ( currentItemRestrictions ) ) { continue ; } // skip primitives
113+
114+ if ( QCConfigurationAdapter . isObjectOnly ( currentItemRestrictions ) ) {
115+ innerObjectBlueprint = innerObjectBlueprint === null ?
116+ currentItemRestrictions :
117+ QCConfigurationAdapter . getRestrictionsIntersection (
118+ innerObjectBlueprint ,
119+ currentItemRestrictions
120+ ) ;
121+
122+ continue ;
123+ }
124+
125+ // if the current restrictions describe an array, we intersect object restrictions for them
126+ // with current object restrictions (for previously calculated values)
127+ innerArrayObjectBlueprint =
128+ innerArrayObjectBlueprint === null
129+ ? currentItemRestrictions
130+ : QCConfigurationAdapter . getRestrictionsIntersection (
131+ innerArrayObjectBlueprint ,
132+ currentItemRestrictions [ 1 ]
133+ ) ;
134+
135+ // we also intersect array restrictions for them
136+ innerArrayArrayBlueprint =
137+ innerArrayArrayBlueprint === null
138+ ? currentItemRestrictions
139+ : QCConfigurationAdapter . getRestrictionsIntersection (
140+ innerArrayArrayBlueprint ,
141+ currentItemRestrictions [ 2 ]
142+ ) ;
109143 }
110144
111- return [ itemsRestrictions , maximumIntersection ] ;
145+ return [
146+ itemsRestrictions ,
147+ innerObjectBlueprint ,
148+ [ innerArrayObjectBlueprint , innerArrayArrayBlueprint ]
149+ ] ;
112150 } ;
113151
114152 /**
@@ -117,28 +155,57 @@ class QCConfigurationAdapter {
117155 * @param {Restrictions } first
118156 * @param {Restrictions } second
119157 */
120- static getRestrictionIntersection = ( first , second ) => {
121- if ( ! QCConfigurationAdapter . bothAreObjects ( first , second ) ) { return null ; }
158+ static getRestrictionsIntersection = ( first , second ) => {
159+ if ( QCConfigurationAdapter . bothArePrimitive ( first , second ) ) {
160+ // the intersection returns the value type, or null if types are different
161+ return first === second ? first : null ;
162+ }
122163
123- const restrictions = { } ;
124- Object . entries ( first ) . forEach ( ( [ key , val ] ) => {
125- if ( ! ( key in second ) ) { return ; }
126- const maximumIntersection = QCConfigurationAdapter . getRestrictionIntersection ( val , second [ key ] ) ;
127- if ( maximumIntersection === null ) { return ; }
128- restrictions [ key ] = maximumIntersection ;
129- } ) ;
130- return restrictions ;
164+ if ( QCConfigurationAdapter . bothAreArrays ( first , second ) ) {
165+ // intersection of two ArrayRestrictions objects
166+ return QCConfigurationAdapter . getArrayRestrictionsIntersection ( first , second ) ;
167+ }
168+
169+ if ( QCConfigurationAdapter . bothAreObjects ( first , second ) ) {
170+ const restrictions = { } ;
171+ Object . entries ( first ) . forEach ( ( [ key , val ] ) => {
172+ if ( ! ( key in second ) ) { return ; }
173+ const maximumIntersection = QCConfigurationAdapter . getRestrictionsIntersection ( val , second [ key ] ) ;
174+ if ( maximumIntersection === null ) { return ; }
175+ restrictions [ key ] = maximumIntersection ;
176+ } ) ;
177+ return restrictions ;
178+ }
179+
180+ return null ;
131181 } ;
132182
183+ static getArrayRestrictionsIntersection = ( first , second ) => {
184+ return [
185+ [ ] ,
186+ QCConfigurationAdapter . getRestrictionsIntersection ( first [ 1 ] , second [ 1 ] ) ,
187+ QCConfigurationAdapter . getRestrictionsIntersection ( first [ 2 ] , second [ 2 ] ) ,
188+ ] ;
189+ } ;
190+
191+ static isPrimitive = ( value ) => typeof value === 'string' ;
192+
193+ static isObjectOnly = ( value ) => (
194+ typeof value === 'object' &&
195+ value !== null &&
196+ ! Array . isArray ( value )
197+ ) ;
198+
199+ static bothArePrimitive = ( first , second ) => {
200+ return QCConfigurationAdapter . isPrimitive ( first ) && QCConfigurationAdapter . isPrimitive ( second ) ;
201+ }
202+
203+ static bothAreArrays = ( first , second ) => {
204+ return Array . isArray ( first ) && Array . isArray ( second ) ;
205+ }
206+
133207 static bothAreObjects = ( first , second ) => {
134- return (
135- typeof first === 'object' &&
136- typeof second === 'object' &&
137- first !== null &&
138- second !== null &&
139- ! Array . isArray ( first ) &&
140- ! Array . isArray ( second )
141- ) ;
208+ return QCConfigurationAdapter . isObjectOnly ( first ) && QCConfigurationAdapter . isObjectOnly ( second ) ;
142209 } ;
143210}
144211
0 commit comments