@@ -2030,9 +2030,12 @@ Document.prototype.validate = function(pathsToValidate, options, callback) {
2030
2030
let parallelValidate ;
2031
2031
2032
2032
if ( this . $__ . validating ) {
2033
- parallelValidate = new ParallelValidateError ( this ) ;
2033
+ parallelValidate = new ParallelValidateError ( this , {
2034
+ parentStack : options && options . parentStack ,
2035
+ conflictStack : this . $__ . validating . stack
2036
+ } ) ;
2034
2037
} else {
2035
- this . $__ . validating = new ParallelValidateError ( this ) ;
2038
+ this . $__ . validating = new ParallelValidateError ( this , { parentStack : options && options . parentStack } ) ;
2036
2039
}
2037
2040
2038
2041
if ( typeof pathsToValidate === 'function' ) {
@@ -2076,53 +2079,54 @@ function _evaluateRequiredFunctions(doc) {
2076
2079
*/
2077
2080
2078
2081
function _getPathsToValidate ( doc ) {
2079
- let i ;
2080
- let len ;
2081
2082
const skipSchemaValidators = { } ;
2082
2083
2083
2084
_evaluateRequiredFunctions ( doc ) ;
2084
2085
2085
2086
// only validate required fields when necessary
2086
- let paths = Object . keys ( doc . $__ . activePaths . states . require ) . filter ( function ( path ) {
2087
+ let paths = new Set ( Object . keys ( doc . $__ . activePaths . states . require ) . filter ( function ( path ) {
2087
2088
if ( ! doc . isSelected ( path ) && ! doc . isModified ( path ) ) {
2088
2089
return false ;
2089
2090
}
2090
2091
if ( path in doc . $__ . cachedRequired ) {
2091
2092
return doc . $__ . cachedRequired [ path ] ;
2092
2093
}
2093
2094
return true ;
2094
- } ) ;
2095
+ } ) ) ;
2096
+
2095
2097
2096
- paths = paths . concat ( Object . keys ( doc . $__ . activePaths . states . init ) ) ;
2097
- paths = paths . concat ( Object . keys ( doc . $__ . activePaths . states . modify ) ) ;
2098
- paths = paths . concat ( Object . keys ( doc . $__ . activePaths . states . default ) ) ;
2098
+ function addToPaths ( p ) { paths . add ( p ) ; }
2099
+ Object . keys ( doc . $__ . activePaths . states . init ) . forEach ( addToPaths ) ;
2100
+ Object . keys ( doc . $__ . activePaths . states . modify ) . forEach ( addToPaths ) ;
2101
+ Object . keys ( doc . $__ . activePaths . states . default ) . forEach ( addToPaths ) ;
2099
2102
2100
2103
const subdocs = doc . $__getAllSubdocs ( ) ;
2101
- let subdoc ;
2102
- len = subdocs . length ;
2103
2104
const modifiedPaths = doc . modifiedPaths ( ) ;
2104
- for ( i = 0 ; i < len ; ++ i ) {
2105
- subdoc = subdocs [ i ] ;
2106
- if ( subdoc . $basePath &&
2107
- doc . isModified ( subdoc . $basePath , modifiedPaths ) &&
2108
- ! doc . isDirectModified ( subdoc . $basePath ) &&
2109
- ! doc . $isDefault ( subdoc . $basePath ) ) {
2105
+ for ( const subdoc of subdocs ) {
2106
+ if ( subdoc . $basePath ) {
2110
2107
// Remove child paths for now, because we'll be validating the whole
2111
2108
// subdoc
2112
- paths = paths . filter ( function ( p ) {
2113
- return p != null && p . indexOf ( subdoc . $basePath + '.' ) !== 0 ;
2114
- } ) ;
2115
- paths . push ( subdoc . $basePath ) ; // This can cause duplicates, make unique below
2116
- skipSchemaValidators [ subdoc . $basePath ] = true ;
2109
+ for ( const p of paths ) {
2110
+ if ( p === null || p . startsWith ( subdoc . $basePath + '.' ) ) {
2111
+ paths . delete ( p ) ;
2112
+ }
2113
+ }
2114
+
2115
+ if ( doc . isModified ( subdoc . $basePath , modifiedPaths ) &&
2116
+ ! doc . isDirectModified ( subdoc . $basePath ) &&
2117
+ ! doc . $isDefault ( subdoc . $basePath ) ) {
2118
+ paths . add ( subdoc . $basePath ) ;
2119
+
2120
+ skipSchemaValidators [ subdoc . $basePath ] = true ;
2121
+ }
2117
2122
}
2118
2123
}
2119
2124
2125
+ // from here on we're not removing items from paths
2126
+
2120
2127
// gh-661: if a whole array is modified, make sure to run validation on all
2121
2128
// the children as well
2122
- len = paths . length ;
2123
- for ( i = 0 ; i < len ; ++ i ) {
2124
- const path = paths [ i ] ;
2125
-
2129
+ for ( const path of paths ) {
2126
2130
const _pathType = doc . schema . path ( path ) ;
2127
2131
if ( ! _pathType ||
2128
2132
! _pathType . $isMongooseArray ||
@@ -2144,34 +2148,33 @@ function _getPathsToValidate(doc) {
2144
2148
if ( Array . isArray ( val [ j ] ) ) {
2145
2149
_pushNestedArrayPaths ( val [ j ] , paths , path + '.' + j ) ;
2146
2150
} else {
2147
- paths . push ( path + '.' + j ) ;
2151
+ paths . add ( path + '.' + j ) ;
2148
2152
}
2149
2153
}
2150
2154
}
2151
2155
}
2152
2156
2153
2157
const flattenOptions = { skipArrays : true } ;
2154
- len = paths . length ;
2155
- for ( i = 0 ; i < len ; ++ i ) {
2156
- const pathToCheck = paths [ i ] ;
2158
+ for ( const pathToCheck of paths ) {
2157
2159
if ( doc . schema . nested [ pathToCheck ] ) {
2158
2160
let _v = doc . $__getValue ( pathToCheck ) ;
2159
2161
if ( isMongooseObject ( _v ) ) {
2160
2162
_v = _v . toObject ( { transform : false } ) ;
2161
2163
}
2162
2164
const flat = flatten ( _v , pathToCheck , flattenOptions , doc . schema ) ;
2163
- paths = paths . concat ( Object . keys ( flat ) ) ;
2165
+ Object . keys ( flat ) . forEach ( addToPaths ) ;
2164
2166
}
2165
2167
}
2166
2168
2167
- // Single nested paths (paths embedded under single nested subdocs) will
2168
- // be validated on their own when we call `validate()` on the subdoc itself.
2169
- // Re: gh-8468
2170
- paths = paths . filter ( p => ! doc . schema . singleNestedPaths . hasOwnProperty ( p ) ) ;
2171
2169
2172
- len = paths . length ;
2173
- for ( i = 0 ; i < len ; ++ i ) {
2174
- const path = paths [ i ] ;
2170
+ for ( const path of paths ) {
2171
+ // Single nested paths (paths embedded under single nested subdocs) will
2172
+ // be validated on their own when we call `validate()` on the subdoc itself.
2173
+ // Re: gh-8468
2174
+ if ( doc . schema . singleNestedPaths . hasOwnProperty ( path ) ) {
2175
+ paths . delete ( path ) ;
2176
+ continue ;
2177
+ }
2175
2178
const _pathType = doc . schema . path ( path ) ;
2176
2179
if ( ! _pathType || ! _pathType . $isSchemaMap ) {
2177
2180
continue ;
@@ -2182,11 +2185,11 @@ function _getPathsToValidate(doc) {
2182
2185
continue ;
2183
2186
}
2184
2187
for ( const key of val . keys ( ) ) {
2185
- paths . push ( path + '.' + key ) ;
2188
+ paths . add ( path + '.' + key ) ;
2186
2189
}
2187
2190
}
2188
2191
2189
- paths = Array . from ( new Set ( paths ) ) ;
2192
+ paths = Array . from ( paths ) ;
2190
2193
return [ paths , skipSchemaValidators ] ;
2191
2194
}
2192
2195
@@ -2208,6 +2211,15 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
2208
2211
( typeof options === 'object' ) &&
2209
2212
( 'validateModifiedOnly' in options ) ;
2210
2213
2214
+ const saveDeepStacktrace = (
2215
+ options &&
2216
+ ( typeof options === 'object' ) &&
2217
+ ! ! options [ 'deepStackTrace' ] )
2218
+ || ! ! this . schema . options . deepStackTrace ;
2219
+ const parentStack = saveDeepStacktrace && options &&
2220
+ ( typeof options === 'object' ) &&
2221
+ options . parentStack || [ ] ;
2222
+
2211
2223
let shouldValidateModifiedOnly ;
2212
2224
if ( hasValidateModifiedOnlyOption ) {
2213
2225
shouldValidateModifiedOnly = ! ! options . validateModifiedOnly ;
@@ -2294,6 +2306,9 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
2294
2306
validated [ path ] = true ;
2295
2307
total ++ ;
2296
2308
2309
+ const stackToHere = saveDeepStacktrace ?
2310
+ [ ( new Error ( ) . stack ) , path ] . concat ( parentStack ) : void 0 ;
2311
+
2297
2312
process . nextTick ( function ( ) {
2298
2313
const p = _this . schema . path ( path ) ;
2299
2314
@@ -2320,6 +2335,11 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
2320
2335
_this . $__ . pathsToScopes [ path ] :
2321
2336
_this ;
2322
2337
2338
+ const doValidateOptions = {
2339
+ skipSchemaValidators : skipSchemaValidators [ path ] ,
2340
+ path : path ,
2341
+ parentStack : stackToHere
2342
+ } ;
2323
2343
p . doValidate ( val , function ( err ) {
2324
2344
if ( err && ( ! p . $isMongooseDocumentArray || err . $isArrayValidatorError ) ) {
2325
2345
if ( p . $isSingleNested &&
@@ -2330,7 +2350,7 @@ Document.prototype.$__validate = function(pathsToValidate, options, callback) {
2330
2350
_this . invalidate ( path , err , undefined , true ) ;
2331
2351
}
2332
2352
-- total || complete ( ) ;
2333
- } , scope , { skipSchemaValidators : skipSchemaValidators [ path ] , path : path } ) ;
2353
+ } , scope , doValidateOptions ) ;
2334
2354
} ) ;
2335
2355
} ;
2336
2356
0 commit comments