@@ -10,66 +10,86 @@ const debugCORS = Debug("mws:cors");
1010
1111
1212
13- export const registerZodRoutes = ( parent : ServerRoute , router : any , keys : string [ ] ) => {
14- // const router = new TiddlerRouter();
15- keys . forEach ( ( key ) => {
16- const route = router [ key as keyof typeof router ] as ZodRoute < any , any , any , any , any , any > ;
17- const {
18- method, path, bodyFormat, registerError,
19- zodPathParams,
20- zodQueryParams = ( z => ( { } ) as any ) ,
21- zodRequestBody = [ "string" , "json" , "www-form-urlencoded" ] . includes ( bodyFormat )
22- ? z => z . undefined ( ) : ( z => z . any ( ) as any ) ,
23- inner,
24- securityChecks,
25- } = route ;
26-
27- if ( method . includes ( "OPTIONS" ) )
28- throw new Error ( key + " includes OPTIONS. Use corsRequest instead." ) ;
29-
30- const pathParams = path . split ( "/" ) . filter ( e => e . startsWith ( ":" ) ) . map ( e => e . substring ( 1 ) ) ;
31- ///^\/recipes\/([^\/]+)\/tiddlers\/(.+)$/,
32- if ( ! path . startsWith ( "/" ) ) throw new Error ( `Path ${ path } must start with a forward slash` ) ;
33- if ( key . startsWith ( ":" ) ) throw new Error ( `Key ${ key } must not start with a colon` )
34- const pathregex = "^" + path . split ( "/" ) . map ( e =>
35- e === "$key" ? key : e . startsWith ( ":" ) ? "([^/]+)" : e
36- ) . join ( "\\/" ) + "$" ;
37-
38- parent . defineRoute ( {
39- method,
40- path : new RegExp ( pathregex ) ,
41- pathParams,
42- bodyFormat,
43- denyFinal : false ,
44- securityChecks,
45- } , async state => {
46-
47- checkPath ( state , zodPathParams , registerError ) ;
48-
49- checkQuery ( state , zodQueryParams , registerError ) ;
50-
51- checkData ( state , zodRequestBody , registerError ) ;
52-
53- const timekey = `handler ${ state . bodyFormat } ${ state . method } ${ state . urlInfo . pathname } ` ;
54- if ( Debug . enabled ( "server:handler:timing" ) ) console . time ( timekey ) ;
55- const [ good , error , res ] = await inner ( state )
56- . then ( e => [ true , undefined , e ] as const , e => [ false , e , undefined ] as const ) ;
57- if ( Debug . enabled ( "server:handler:timing" ) ) console . timeEnd ( timekey ) ;
58-
59- if ( ! good ) {
60- if ( error === STREAM_ENDED ) {
61- return error ;
62- } else if ( typeof error === "string" ) {
63- return state . sendString ( 400 , { "x-reason" : "zod-handler" } , error , "utf8" ) ;
64- } else if ( error instanceof Error && error . name === "UserError" ) {
65- return state . sendString ( 400 , { "x-reason" : "user-error" } , error . message , "utf8" ) ;
66- } else {
67- throw error ;
68- }
13+ export const registerZodRoutes = ( parent : ServerRoute , router : any , keys : string [ ] , keyReplacer : string = "$key" ) => {
14+ return keys . map ( ( key ) => {
15+ defineZodRoute ( parent , key , keyReplacer , router [ key ] ) ;
16+ } ) ;
17+ }
18+
19+ function buildPathRegex ( path : string , key : string , keyReplacer : string ) {
20+ if ( ! path . startsWith ( "/" ) ) throw new Error ( `Path ${ path } must start with a forward slash` ) ;
21+ if ( key . startsWith ( ":" ) ) throw new Error ( `Key ${ key } must not start with a colon` )
22+ if ( path !== path . trim ( ) ) throw new Error ( `Path ${ path } must not have leading and trailing white space or line terminator characters` ) ;
23+ const parts = path . split ( "/" ) ;
24+ const final = path . endsWith ( "/" ) ;
25+ return "^" + parts . map ( ( e , i ) => {
26+
27+ const last = i === parts . length - 1 ;
28+ if ( e . length === 0 ) {
29+ if ( ! last && i !== 0 ) throw new Error ( `Path ${ path } has an empty part at index ${ i } ` ) ;
30+ return "" ;
31+ }
32+ const name = e . startsWith ( ":" ) && e . slice ( 1 ) ;
33+ if ( e === keyReplacer ) return key ;
34+ if ( ! name ) return e ;
35+ return ( last && final ) ? `(?<${ name } >.+)` : `(?<${ name } >[^/]+)` ;
36+ } ) . join ( "\\/" ) + ( final ? "$" : "(?=\/)" ) ;
37+ }
38+
39+ export function defineZodRoute (
40+ parent : ServerRoute ,
41+ key : string ,
42+ keyReplacer : string ,
43+ route : ZodRoute < any , any , any , any , any , any >
44+ ) {
45+ const {
46+ method, path, bodyFormat, registerError,
47+ zodPathParams,
48+ zodQueryParams = ( z => ( { } ) as any ) ,
49+ zodRequestBody = [ "string" , "json" , "www-form-urlencoded" ] . includes ( bodyFormat )
50+ ? z => z . undefined ( ) : ( z => z . any ( ) as any ) ,
51+ inner,
52+ securityChecks,
53+ } = route ;
54+
55+ if ( method . includes ( "OPTIONS" ) )
56+ throw new Error ( key + " includes OPTIONS. Use corsRequest instead." ) ;
57+
58+ const pathregex = typeof path === "string" ? buildPathRegex ( path , key , keyReplacer ) : path ;
59+
60+ return parent . defineRoute ( {
61+ method,
62+ path : new RegExp ( pathregex ) ,
63+ bodyFormat,
64+ denyFinal : false ,
65+ securityChecks,
66+ } , async state => {
67+
68+ checkPath ( state , zodPathParams , registerError ) ;
69+
70+ checkQuery ( state , zodQueryParams , registerError ) ;
71+
72+ checkData ( state , zodRequestBody , registerError ) ;
73+
74+ const timekey = `handler ${ state . bodyFormat } ${ state . method } ${ state . urlInfo . pathname } ` ;
75+ if ( Debug . enabled ( "server:handler:timing" ) ) console . time ( timekey ) ;
76+ const [ good , error , res ] = await inner ( state )
77+ . then ( e => [ true , undefined , e ] as const , e => [ false , e , undefined ] as const ) ;
78+ if ( Debug . enabled ( "server:handler:timing" ) ) console . timeEnd ( timekey ) ;
79+
80+ if ( ! good ) {
81+ if ( error === STREAM_ENDED ) {
82+ return error ;
83+ } else if ( typeof error === "string" ) {
84+ return state . sendString ( 400 , { "x-reason" : "zod-handler" } , error , "utf8" ) ;
85+ } else if ( error instanceof Error && error . name === "UserError" ) {
86+ return state . sendString ( 400 , { "x-reason" : "user-error" } , error . message , "utf8" ) ;
87+ } else {
88+ throw error ;
6989 }
90+ }
7091
71- return state . sendJSON ( 200 , res ) ;
72- } ) ;
92+ return state . sendJSON ( 200 , res ) ;
7393 } ) ;
7494}
7595
0 commit comments