@@ -55,6 +55,12 @@ async function parseExpressCookie(req): Promise<
5555}
5656
5757const EXPRESS_ROUTE_SCHEMA = Symbol ( 'adminforth.express.withSchema' ) ;
58+ const EXPRESS_REGEXP_LEADING_SLASH_RE = / ^ \\ \/ / ;
59+ const EXPRESS_REGEXP_OPTIONAL_TRAILING_SLASH_RE = / \\ \/ \? \( \? = \\ \/ \| \$ \) \$ $ / ;
60+ const EXPRESS_REGEXP_PARAM_CAPTURE_RE = / \( \? : \( \[ \^ \\ \/ ] \+ \? \) \) / g;
61+ const EXPRESS_REGEXP_ESCAPED_SLASH_RE = / \\ \/ / g;
62+ const EXPRESS_REGEXP_TRAILING_DOLLAR_RE = / \$ $ / ;
63+ const EXPRESS_REGEXP_LEADING_CARET_RE = / ^ \^ / ;
5864
5965type RegisteredExpressRouteSchema = IAdminForthExpressRouteSchema & {
6066 request ?: AnySchemaObject ;
@@ -91,6 +97,16 @@ function normalizeExpressRuntimeSchema(schema: unknown): AnySchemaObject | undef
9197 return schema as AnySchemaObject ;
9298}
9399
100+ function normalizeExpressLayerRegexpSource ( regexpSource : string ) : string {
101+ return regexpSource
102+ . replace ( EXPRESS_REGEXP_LEADING_SLASH_RE , '/' )
103+ . replace ( EXPRESS_REGEXP_OPTIONAL_TRAILING_SLASH_RE , '' )
104+ . replace ( EXPRESS_REGEXP_PARAM_CAPTURE_RE , ':param' )
105+ . replace ( EXPRESS_REGEXP_ESCAPED_SLASH_RE , '/' )
106+ . replace ( EXPRESS_REGEXP_TRAILING_DOLLAR_RE , '' )
107+ . replace ( EXPRESS_REGEXP_LEADING_CARET_RE , '' ) ;
108+ }
109+
94110const respondNoServer = ( title , explanation ) => {
95111 return `
96112 <!DOCTYPE html>
@@ -255,7 +271,10 @@ class ExpressServer implements IExpressHttpServer {
255271 serve ( app ) {
256272 this . expressApp = app ;
257273 this . patchSchemaAwareRouteRegistration ( ) ;
258- this . registerSchemaAwareExistingRoutes ( ) ;
274+ const stack = ( this . expressApp as any ) ?. _router ?. stack ;
275+ if ( Array . isArray ( stack ) ) {
276+ this . registerSchemaAwareStack ( stack , '' ) ;
277+ }
259278 this . setupWsServer ( ) ;
260279 this . adminforth . setupEndpoints ( this ) ;
261280 this . setupOpenApiRoutes ( ) ;
@@ -374,24 +393,25 @@ class ExpressServer implements IExpressHttpServer {
374393 } ) as any ;
375394 } ) ;
376395
377- this . schemaAwareRouteRegistrationPatched = true ;
378- }
379-
380- registerSchemaAwareExistingRoutes ( ) {
381- const stack = ( this . expressApp as any ) ?. _router ?. stack ;
382- if ( ! Array . isArray ( stack ) ) {
383- return ;
396+ const originalUse = this . expressApp . use ?. bind ( this . expressApp ) ;
397+ if ( originalUse ) {
398+ this . expressApp . use = ( ( ...args ) => {
399+ const [ firstArg , ...restArgs ] = args ;
400+ const path = typeof firstArg === 'string' || Array . isArray ( firstArg )
401+ ? firstArg
402+ : '' ;
403+ const handlers = path ? restArgs : args ;
404+
405+ this . flattenHandlers ( handlers ) . forEach ( ( handler ) => {
406+ if ( Array . isArray ( ( handler as any ) ?. stack ) ) {
407+ this . registerSchemaAwareStack ( ( handler as any ) . stack , path ) ;
408+ }
409+ } ) ;
410+ return originalUse ( ...args ) ;
411+ } ) as any ;
384412 }
385413
386- stack . forEach ( ( layer ) => {
387- if ( ! layer . route ) {
388- return ;
389- }
390-
391- const methods = Object . keys ( layer . route . methods || { } ) . filter ( ( method ) => layer . route . methods [ method ] ) ;
392- const handlers = ( layer . route . stack || [ ] ) . map ( ( routeLayer ) => routeLayer . handle ) ;
393- this . registerSchemaAwareRoute ( methods , layer . route . path , handlers ) ;
394- } ) ;
414+ this . schemaAwareRouteRegistrationPatched = true ;
395415 }
396416
397417 registerSchemaAwareRoute ( methods , path , handlers ) {
@@ -433,8 +453,61 @@ class ExpressServer implements IExpressHttpServer {
433453 } ) ;
434454 }
435455
456+ registerSchemaAwareStack ( stack , prefix ) {
457+ const prefixes = this . flattenPaths ( prefix ) ;
458+
459+ stack . forEach ( ( layer ) => {
460+ if ( layer . route ) {
461+ const methods = Object . keys ( layer . route . methods || { } ) . filter ( ( method ) => layer . route . methods [ method ] ) ;
462+ const handlers = ( layer . route . stack || [ ] ) . map ( ( routeLayer ) => routeLayer . handle ) ;
463+ this . registerSchemaAwareRoute ( methods , this . combineRoutePaths ( prefixes , layer . route . path ) , handlers ) ;
464+ return ;
465+ }
466+
467+ const nestedStack = layer . handle ?. stack ;
468+ if ( ! Array . isArray ( nestedStack ) ) {
469+ return ;
470+ }
471+
472+ const layerPath = this . extractLayerPath ( layer ) ;
473+ const nestedPrefix = this . combineRoutePaths ( prefixes , layerPath ) ;
474+ this . registerSchemaAwareStack ( nestedStack , nestedPrefix ) ;
475+ } ) ;
476+ }
477+
478+ combineRoutePaths ( prefixes , paths ) {
479+ return prefixes . flatMap ( ( prefix ) => this . flattenPaths ( paths ) . map ( ( path ) => {
480+ if ( ! prefix ) return path || '/' ;
481+ if ( ! path || path === '/' ) return prefix ;
482+ return `${ prefix . endsWith ( '/' ) ? prefix . slice ( 0 , - 1 ) : prefix } ${ path . startsWith ( '/' ) ? path : `/${ path } ` } ` ;
483+ } ) ) ;
484+ }
485+
486+ extractLayerPath ( layer ) {
487+ if ( typeof layer . path === 'string' ) {
488+ return layer . path ;
489+ }
490+
491+ const regexpSource = layer . regexp ?. source ;
492+ if ( typeof regexpSource !== 'string' ) {
493+ return '' ;
494+ }
495+
496+ if ( layer . regexp ?. fast_slash ) {
497+ return '' ;
498+ }
499+
500+ return normalizeExpressLayerRegexpSource ( regexpSource ) ;
501+ }
502+
436503 flattenHandlers ( handlers ) {
437- return handlers . flatMap ( ( handler ) => Array . isArray ( handler ) ? this . flattenHandlers ( handler ) : [ handler ] ) ;
504+ return handlers . flat ( Infinity ) ;
505+ }
506+
507+ flattenPaths ( paths ) {
508+ const flattened = ( Array . isArray ( paths ) ? paths : [ paths ] ) . flat ( Infinity ) ;
509+ const stringPaths = flattened . filter ( ( path ) : path is string => typeof path === 'string' ) ;
510+ return stringPaths . length ? stringPaths : [ '' ] ;
438511 }
439512
440513 setupOpenApiRoutes ( ) {
@@ -590,4 +663,4 @@ class ExpressServer implements IExpressHttpServer {
590663
591664}
592665
593- export default ExpressServer ;
666+ export default ExpressServer ;
0 commit comments