@@ -8,6 +8,7 @@ const MongooseError = require('../error/mongooseError');
88const Readable = require ( 'stream' ) . Readable ;
99const eachAsync = require ( '../helpers/cursor/eachAsync' ) ;
1010const immediate = require ( '../helpers/immediate' ) ;
11+ const kareem = require ( 'kareem' ) ;
1112const util = require ( 'util' ) ;
1213
1314/**
@@ -62,7 +63,11 @@ util.inherits(AggregationCursor, Readable);
6263
6364function _init ( model , c , agg ) {
6465 if ( ! model . collection . buffer ) {
65- model . hooks . execPre ( 'aggregate' , agg , function ( ) {
66+ model . hooks . execPre ( 'aggregate' , agg , function ( err ) {
67+ if ( err != null ) {
68+ _handlePreHookError ( c , err ) ;
69+ return ;
70+ }
6671 if ( typeof agg . options ?. cursor ?. transform === 'function' ) {
6772 c . _transforms . push ( agg . options . cursor . transform ) ;
6873 }
@@ -72,7 +77,12 @@ function _init(model, c, agg) {
7277 } ) ;
7378 } else {
7479 model . collection . emitter . once ( 'queue' , function ( ) {
75- model . hooks . execPre ( 'aggregate' , agg , function ( ) {
80+ model . hooks . execPre ( 'aggregate' , agg , function ( err ) {
81+ if ( err != null ) {
82+ _handlePreHookError ( c , err ) ;
83+ return ;
84+ }
85+
7686 if ( typeof agg . options ?. cursor ?. transform === 'function' ) {
7787 c . _transforms . push ( agg . options . cursor . transform ) ;
7888 }
@@ -84,6 +94,38 @@ function _init(model, c, agg) {
8494 }
8595}
8696
97+ /**
98+ * Handles error emitted from pre middleware. In particular, checks for `skipWrappedFunction`, which allows skipping
99+ * the actual aggregation and overwriting the function's return value. Because aggregation cursors don't return a value,
100+ * we need to make sure the user doesn't accidentally set a value in skipWrappedFunction.
101+ *
102+ * @param {QueryCursor } queryCursor
103+ * @param {Error } err
104+ * @returns
105+ */
106+
107+ function _handlePreHookError ( queryCursor , err ) {
108+ if ( err instanceof kareem . skipWrappedFunction ) {
109+ const resultValue = err . args [ 0 ] ;
110+ if ( resultValue != null && ( ! Array . isArray ( resultValue ) || resultValue . length ) ) {
111+ const err = new MongooseError (
112+ 'Cannot `skipMiddlewareFunction()` with a value when using ' +
113+ '`.aggregate().cursor()`, value must be nullish or empty array, got "' +
114+ util . inspect ( resultValue ) +
115+ '".'
116+ ) ;
117+ queryCursor . _markError ( err ) ;
118+ queryCursor . listeners ( 'error' ) . length > 0 && queryCursor . emit ( 'error' , err ) ;
119+ return ;
120+ }
121+ queryCursor . emit ( 'cursor' , null ) ;
122+ return ;
123+ }
124+ queryCursor . _markError ( err ) ;
125+ queryCursor . listeners ( 'error' ) . length > 0 && queryCursor . emit ( 'error' , err ) ;
126+ }
127+
128+
87129/**
88130 * Necessary to satisfy the Readable API
89131 * @method _read
@@ -424,6 +466,7 @@ function _next(ctx, cb) {
424466 err => callback ( err )
425467 ) ;
426468 } else {
469+ ctx . once ( 'error' , cb ) ;
427470 ctx . once ( 'cursor' , function ( ) {
428471 _next ( ctx , cb ) ;
429472 } ) ;
0 commit comments