"use strict" ;
const util = require ( "util" ) ;
const range = require ( "range" ) . range ;
const fromEntries = require ( "fromentries" ) ;
const { validateArguments , validateValue } = require ( "@validatem/core" ) ;
const isArray = require ( "@validatem/is-array" ) ;
const isPlainObject = require ( "@validatem/is-plain-object" ) ;
const defaultTo = require ( "@validatem/default-to" ) ;
const removeNullishItems = require ( "@validatem/remove-nullish-items" ) ;
const virtualProperty = require ( "@validatem/virtual-property" ) ;
const wrapPath = require ( "@validatem/wrap-path" ) ;
/* NOTE: In some cases below, we explicitly check for `undefined` only, rather than for both `undefined` and `null`. This is to allow explicitly overriding existent values with `null` during a merge. */
function wrapValidationPath ( basePathSegments , lastProperty , rules ) {
let combinedPath = basePathSegments . concat ( virtualProperty ( lastProperty ) ) ;
return wrapPath ( combinedPath , rules ) ;
}
function mapToObject ( items , mapper ) {
return fromEntries ( items . map ( mapper ) ) ;
}
function combineKeys ( ... objects ) {
let allKeys = new Set ( ) ;
for ( let object of objects ) {
for ( let key of Object . keys ( object ) ) {
allKeys . add ( key ) ;
}
}
return Array . from ( allKeys ) ;
}
let optionalArray = [ defaultTo ( [ ] ) , isArray ] ;
let optionalObject = [ defaultTo ( { } ) , isPlainObject ] ;
function mergeArray ( subTemplate , aInput , bInput , path ) {
let aItems = validateValue ( aInput , wrapValidationPath ( path , "a" , [ optionalArray ] ) ) ;
let bItems = validateValue ( bInput , wrapValidationPath ( path , "b" , [ optionalArray ] ) ) ;
let valueRule = subTemplate [ 0 ] ;
if ( valueRule == null ) {
/* No object merging rule specified, so just concatenate the items. */
return aItems . concat ( bItems ) ;
} else {
/* Object merging rule specified, so we should invoke that merging rule for each pair of objects. */
let itemCount = Math . max ( aItems . length , bItems . length ) ;
return range ( 0 , itemCount ) . map ( ( i ) => {
return mergeValue ( valueRule , aItems [ i ] , bItems [ i ] , path . concat ( [ i ] ) ) ;
} ) ;
}
}
function mergeObject ( subTemplate , aInput , bInput , path ) {
let a = validateValue ( aInput , wrapValidationPath ( path , "a" , [ optionalObject ] ) ) ;
let b = validateValue ( bInput , wrapValidationPath ( path , "b" , [ optionalObject ] ) ) ;
let allKeys = combineKeys ( a , b , subTemplate ) ;
return mapToObject ( allKeys , ( key ) => {
let rule = subTemplate [ key ] ;
let value = mergeValue ( rule , a [ key ] , b [ key ] , path . concat ( [ key ] ) ) ;
return [ key , value ] ;
} ) ;
}
function mergeValue ( rule , a , b , path ) {
if ( rule == null ) {
if ( b !== undefined ) {
return b ;
} else {
return a ;
}
} else if ( typeof rule === "function" ) {
if ( a === undefined ) {
return b ;
} else if ( b === undefined ) {
return a ;
} else {
return rule ( a , b ) ;
}
} else if ( typeof rule === "object" ) {
if ( Array . isArray ( rule ) ) {
return mergeArray ( rule , a , b , path ) ;
} else {
return mergeObject ( rule , a , b , path ) ;
}
} else {
throw new Error ( ` Unrecognized rule: ${ util . inspect ( rule ) } ` ) ;
}
}
module . exports = {
createMerger : function createMerger ( template ) {
return function merge ( _items ) {
let [ items ] = validateArguments ( arguments , [
[ "items" , [
isArray ,
removeNullishItems
] ]
] ) ;
return items . slice ( 1 ) . reduce ( ( merged , item ) => {
return mergeValue ( template , merged , item , [ ] ) ;
} , items [ 0 ] ) ;
} ;
} ,
anyProperty : function ( template ) {
/* Used for cases where an object is used like a key->value map */
return function merge ( aInput , bInput , path ) {
let a = validateValue ( aInput , wrapValidationPath ( path , "a" , [ optionalObject ] ) ) ;
let b = validateValue ( bInput , wrapValidationPath ( path , "b" , [ optionalObject ] ) ) ;
let allKeys = combineKeys ( a , b ) ;
return mapToObject ( allKeys , ( key ) => {
let value = mergeValue ( template , a [ key ] , b [ key ] , path . concat ( [ key ] ) ) ;
return [ key , value ] ;
} ) ;
} ;
}
} ;