33var _ = require ( 'lodash' )
44 , AccessError = require ( 'dodo/errors' ) . AccessError
55 , NotFoundError = require ( 'dodo/errors' ) . NotFoundError
6+ , HTTPError = require ( 'dodo/errors' ) . HTTPError
67 , Promise = require ( 'bluebird' )
78 , log = require ( 'dodo/logger' ) . getLogger ( 'dodo-core-features.router' ) ;
89
@@ -42,9 +43,13 @@ function Route(opt) {
4243 */
4344 this . expressMiddleware = [ ] ;
4445 /**
45- * @type {function (Request, Response, next) }
46+ * @type {Object }
4647 */
47- this . handlerFunc = null ;
48+ this . handlerFuncs = { } ;
49+ /**
50+ * @type {Object }
51+ */
52+ this . apiVersioningConfig = opt . apiVersioningConfig ;
4853}
4954
5055/**
@@ -54,7 +59,7 @@ function Route(opt) {
5459 * @returns {Route }
5560 */
5661Route . prototype . middleware = function ( middleware ) {
57- if ( this . handlerFunc ) {
62+ if ( _ . size ( this . handlerFuncs ) > 0 ) {
5863 throw new Error ( 'You must call middleware(func) before handler(func)' ) ;
5964 }
6065
@@ -70,7 +75,7 @@ Route.prototype.middleware = function (middleware) {
7075 * @returns {Route }
7176 */
7277Route . prototype . auth = function ( authHandler ) {
73- if ( this . handlerFunc ) {
78+ if ( _ . size ( this . handlerFuncs ) > 0 ) {
7479 throw new Error ( 'You must call auth(func) before handler(func)' ) ;
7580 }
7681
@@ -108,27 +113,189 @@ Route.prototype.customResponse = function () {
108113/**
109114 * Installs a handler for the route.
110115 *
111- * @see Router#get for examples.
116+ * @private
112117 * @param {function(IncomingMessage, ServerResponse, Next) } handler
118+ * @param {number } apiVersion
119+ * @param {boolean } isDefault
113120 * @returns {Route }
114121 */
115- Route . prototype . handler = function ( handler ) {
116- if ( this . handlerFunc ) {
117- throw new Error ( 'handler(func) can be called just once per Route instance' ) ;
122+ Route . prototype . handler_ = function ( handler , apiVersion , isDefault ) {
123+ var self = this ;
124+
125+ if ( self . isApiVersioningEnabled_ ( ) === false && ! _ . isNil ( apiVersion ) ) {
126+ throw new Error ( 'cant define versioned handler because api versioning is not enabled' ) ;
127+ }
128+
129+ // Set the default handler is needed (if called without apiVersion, or with isDefault=true)
130+ if ( _ . isNil ( apiVersion ) || isDefault === true ) {
131+ var existingHandler = self . handlerFuncs [ 'default' ] ;
132+
133+ if ( existingHandler ) {
134+ throw new Error ( 'default handler func can be set only once per Route instance' ) ;
135+ } else {
136+ self . handlerFuncs [ 'default' ] = handler ;
137+ }
138+ }
139+
140+ // Set the api version handler if needed (if apiVersion is defined)
141+ if ( ! _ . isNil ( apiVersion ) ) {
142+ self . validateApiVersion_ ( apiVersion ) ;
143+
144+ var existingHandler = self . handlerFuncs [ apiVersion ] ;
145+
146+ if ( existingHandler ) {
147+ throw new Error ( 'apiVersion handler already exists, can be set only once.' ) ;
148+ } else {
149+ self . handlerFuncs [ apiVersion ] = handler ;
150+ }
118151 }
119152
120- this . handlerFunc = handler ;
121153 this . execute_ ( ) ;
122154 return this ;
123155} ;
124156
157+ /**
158+ * Installs a handler for the route.
159+ *
160+ * @see Router#get for examples.
161+ * @param {function(IncomingMessage, ServerResponse, Next) } handler
162+ * @param {number } [apiVersion]
163+ * @returns {Route }
164+ */
165+ Route . prototype . handler = function ( arg1 , arg2 ) {
166+ var self = this ;
167+ var numberOfArguments = arguments . length ;
168+ if ( numberOfArguments <= 0 || numberOfArguments > 2 ) {
169+ throw new Error ( 'Wrong number of arguments passed to .handler()' )
170+ }
171+ if ( numberOfArguments == 1 ) {
172+ return self . handler_ ( arg1 , undefined , false ) ;
173+ } else {
174+ return self . handler_ ( arg2 , arg1 , false ) ;
175+ }
176+ } ;
177+
178+ /**
179+ * Installs a default handler for the route.
180+ *
181+ * @see Router#get for examples.
182+ * @param {function(IncomingMessage, ServerResponse, Next) } handler
183+ * @param {number } [apiVersion]
184+ * @returns {Route }
185+ */
186+ Route . prototype . defaultHandler = function ( arg1 , arg2 ) {
187+ var self = this ;
188+ var numberOfArguments = arguments . length ;
189+ if ( numberOfArguments <= 0 || numberOfArguments > 2 ) {
190+ throw new Error ( 'Wrong number of arguments passed to .defaultHandler()' )
191+ }
192+ if ( numberOfArguments == 1 ) {
193+ return self . handler_ ( arg1 , undefined , true ) ;
194+ } else {
195+ return self . handler_ ( arg2 , arg1 , true ) ;
196+ }
197+ } ;
198+
199+ /**
200+ * Finds handler for specified api version (optional)
201+ * If apiVersion is not provided, returns the default handler
202+ *
203+ * @private
204+ * @param {number } [apiVersion]
205+ * @returns {function (Request, Response, next) }
206+ */
207+ Route . prototype . findHandler_ = function ( apiVersion , fallbackToDefault , fallbackToPrevious ) {
208+ var self = this ;
209+ var handler ;
210+
211+ if ( _ . isNil ( apiVersion ) ) {
212+ if ( fallbackToDefault ) {
213+ handler = self . handlerFuncs [ 'default' ] ;
214+ }
215+ } else {
216+ // Try to find handler for api version
217+ var handler = self . handlerFuncs [ apiVersion ] ;
218+
219+ // If handler is not found, and fallbackToPrevious config is true
220+ if ( ! handler && fallbackToPrevious ) {
221+ // Find previous api version from handlerFuncs keys
222+ var previousApiVersion = _ . chain ( self . handlerFuncs )
223+ . omit ( 'default' ) // Omit default handler
224+ . keys ( ) // Get apiVersions
225+ . filter ( function ( key ) { // We don't want to include newer api versions than initially requested
226+ return key <= apiVersion ;
227+ } )
228+ . max ( ) // Get the previous existing api version from the subset
229+ . value ( ) ;
230+ if ( previousApiVersion !== undefined ) {
231+ handler = self . handlerFuncs [ previousApiVersion ] ;
232+ }
233+ }
234+ }
235+ return handler ;
236+ } ;
237+
238+ /**
239+ * Tries to find apiVersion from request. Validates it if needed.
240+ *
241+ * @private
242+ * @param {Object } req
243+ * @returns {number }
244+ */
245+ Route . prototype . findApiVersionFromRequest_ = function ( req ) {
246+ var self = this ;
247+ var fallbackToDefault = _ . get ( self , 'apiVersioningConfig.fallbackToDefaultHandler' , true ) ;
248+ var apiVersion = self . apiVersioningConfig . findApiVersionHandler ( req ) ;
249+
250+ // If apiVersion is not defined in request, and fallback is not allowed, thrown an Error.
251+ if ( apiVersion === undefined && fallbackToDefault === false ) {
252+ throw new NotFoundError ( 'Api version must be defined' ) ;
253+ // If apiVersion is defined, validate it
254+ } else if ( apiVersion !== undefined ) {
255+ self . validateApiVersion_ ( apiVersion ) ;
256+ } else {
257+ // Api version is not defined in request, but fallback to default handler is allowed. Pass.
258+ }
259+
260+ return apiVersion ;
261+ } ;
262+
263+ /**
264+ * Validates provided api version against apiVersionConfig availableApiVersions array
265+ *
266+ * @private
267+ * @param {Object } req
268+ */
269+ Route . prototype . validateApiVersion_ = function ( apiVersion ) {
270+ var self = this ;
271+ var availableApiVersions = _ . get ( self , 'apiVersioningConfig.availableApiVersions' ) ;
272+ if ( availableApiVersions && ! availableApiVersions . includes ( apiVersion ) ) {
273+ throw new NotFoundError ( 'specified apiVersion not available. Available api versions are: ' + availableApiVersions . join ( ', ' ) ) ;
274+ }
275+ } ;
276+
277+ /**
278+ * Finds if api versioning is enabled
279+ *
280+ * @private
281+ */
282+ Route . prototype . isApiVersioningEnabled_ = function ( ) {
283+ var self = this ;
284+ return _ . get ( self , 'apiVersioningConfig.enabled' ) === true ;
285+ } ;
286+
125287/**
126288 * @private
127289 */
128290Route . prototype . execute_ = function ( ) {
129291 var self = this ;
130292
131- this . expressRouter [ this . method ] ( this . path , function ( req , res , next ) {
293+ var path = this . path ;
294+ if ( self . isApiVersioningEnabled_ ( ) && _ . isFunction ( self . apiVersioningConfig . generateRoutePathHandler ) ) {
295+ path = self . apiVersioningConfig . generateRoutePathHandler ( path ) ;
296+ }
297+
298+ this . expressRouter [ this . method ] ( path , function ( req , res , next ) {
132299 // return for testing purposes...
133300 return self . handlerMiddleware_ ( req , res , next ) ;
134301 } ) ;
@@ -213,7 +380,22 @@ Route.prototype.handle_ = function (req, res, next) {
213380 } ) ;
214381
215382 return promise . then ( function ( ) {
216- var result = self . handlerFunc . call ( context , req , res , next ) ;
383+ var apiVersion = undefined ;
384+
385+ if ( self . isApiVersioningEnabled_ ( ) ) {
386+ // Try to find api version from request. This also validates it and may throw an error.
387+ apiVersion = self . findApiVersionFromRequest_ ( req ) ;
388+ }
389+ var handler = self . findHandler_ (
390+ apiVersion ,
391+ _ . get ( self , 'apiVersioningConfig.fallbackToDefaultHandler' , true ) ,
392+ _ . get ( self , 'apiVersioningConfig.fallbackToPreviousApiVersion' , true )
393+ ) ;
394+ if ( ! handler ) {
395+ throw new NotFoundError ( 'Handler not found' ) ;
396+ }
397+
398+ var result = handler . call ( context , req , res , next ) ;
217399 if ( self . _omitResultHandlers ) {
218400 return Promise . resolve ( result ) . then ( function ( ) {
219401 return NO_RESULT ;
0 commit comments