@@ -22,6 +22,7 @@ type RequestMethod =
2222 | 'UNLOCK'
2323 | 'PROPFIND'
2424 | 'VIEW' ;
25+ type Options = { url :string , init :RequestInit } ;
2526export interface RequestArgument {
2627 name : string ;
2728 value : any ;
@@ -47,99 +48,221 @@ const JSONStringifyReplacer = function(this:any, key:string, value:any) {
4748 return value ;
4849}
4950
50- export class RestClient {
51+ export class RestClientRequest < ReturnType = any > {
5152 private readonly _baseUrl : string ;
53+ private readonly _path : string ;
54+ private readonly _method : RequestMethod ;
55+ private readonly _requestArguments : RequestArgument [ ] ;
56+ private readonly _headers : { [ key : string ] : string } = { } ;
5257
53- /**
54- * Initialise rest client
55- *
56- * @param {string } baseUrl
57- */
58- constructor ( baseUrl : string ) {
59- if ( ! baseUrl ) {
60- baseUrl = '/' ;
61- }
62-
63- if ( ! baseUrl . endsWith ( '/' ) ) {
64- baseUrl += '/' ;
58+ constructor ( baseUrl : string , method : RequestMethod , path : string , requestArguments : RequestArgument [ ] ) {
59+ while ( path . startsWith ( '/' ) ) {
60+ path = path . substring ( 1 ) ;
6561 }
6662
6763 this . _baseUrl = baseUrl ;
64+ this . _path = path ;
65+ this . _method = method ;
66+ this . _requestArguments = requestArguments ;
6867 }
6968
70- /**
71- * Executes a request to the specified path using the specified method.
72- *
73- * @param {RequestMethod } method The HTTP method to use for the request.
74- * @param {string } path The path of the resource to request.
75- * @param {RequestArgument[] } requestArguments An array of request arguments.
76- * @return {Promise<ReturnData | null> } The result of the request, or null if the response status is 404.
77- */
78- async execute < ReturnData = any > ( method : RequestMethod , path : string , requestArguments : RequestArgument [ ] = [ ] ) {
79- while ( path . startsWith ( '/' ) ) {
80- path = path . substring ( 1 ) ;
69+ public get url ( ) {
70+ return this . _baseUrl + this . _path ;
71+ }
72+
73+ public get method ( ) {
74+ return this . _method ;
75+ }
76+
77+ public get arguments ( ) {
78+ return [ ...this . _requestArguments ] ;
79+ }
80+
81+ public get headers ( ) {
82+ return {
83+ ...this . _headers ,
84+ } ;
85+ }
86+
87+ public withHeader ( name : string , value : string ) {
88+ this . _headers [ name ] = value ;
89+ return this ;
90+ }
91+
92+ public withAuthorization ( auth : string ) {
93+ return this . withHeader ( 'Authorization' , auth ) ;
94+ }
95+
96+ public withBearerToken ( token : string ) {
97+ return this . withAuthorization ( `Bearer ${ token } ` ) ;
98+ }
99+
100+ public withContentType ( contentType : string ) {
101+ return this . withHeader ( 'Content-Type' , contentType ) ;
102+ }
103+
104+ public async call ( ) :Promise < ReturnType | null > {
105+ const opts = this . createOptions ( ) ;
106+ const result = await fetch ( opts . url , opts . init ) ;
107+
108+ if ( result . status === 404 ) {
109+ return null ;
110+ }
111+
112+ let output : ReturnType | null = null ;
113+ if ( result . headers . get ( 'content-type' ) ?. startsWith ( 'application/json' ) ) {
114+ //Only parse json if content-type is application/json
115+ const text = await result . text ( ) ;
116+ output = text ? ( JSON . parse ( text ) as ReturnType ) : null ;
81117 }
82118
83- let url = this . _baseUrl + path ;
119+ if ( result . status >= 400 ) {
120+ const error =
121+ output && typeof output === 'object' && 'error' in output && typeof output . error === 'string'
122+ ? output . error
123+ : 'Unknown error' ;
124+ throw new RestError ( error , result ) ;
125+ }
84126
85- const query : string [ ] = [ ] ;
86- const headers : { [ key : string ] : string } = {
127+ return output ;
128+ }
129+
130+ private createOptions ( ) :Options {
131+ const query : string [ ] = [ ]
132+ const headers = new Headers ( {
133+ ...this . _headers ,
87134 accept : 'application/json' ,
88- } ;
89- const opts : RequestInit = {
90- method,
91- headers,
135+ } ) ;
136+ const out :Options = {
137+ url : this . url ,
138+ init : {
139+ method : this . method ,
140+ headers,
141+ }
92142 } ;
93143
94- requestArguments . forEach ( ( requestArgument ) => {
95- switch ( requestArgument . transport . toLowerCase ( ) ) {
144+ this . _requestArguments . forEach ( ( requestArgument ) => {
145+ const transport = requestArgument . transport ?. toLowerCase ( ) as Lowercase < RequestArgumentTransport > ;
146+ switch ( transport ) {
96147 case 'path' :
97- url = url . replaceAll ( '{' + requestArgument . name + '}' , requestArgument . value ) ;
148+ out . url = out . url . replace ( '{' + requestArgument . name + '}' , requestArgument . value ) ;
98149 break ;
99150 case 'header' :
100- headers [ requestArgument . name ] = requestArgument . value ;
151+ headers . set ( requestArgument . name , requestArgument . value ) ;
101152 break ;
102153 case 'body' :
103- if ( ! headers [ 'content-type' ] ) {
104- headers [ 'content-type' ] = 'application/json' ;
154+ if ( ! headers . has ( 'content-type' ) ) {
155+ headers . set ( 'content-type' , 'application/json' ) ;
105156 }
106- opts . body = JSON . stringify ( requestArgument . value , JSONStringifyReplacer ) ;
157+ out . init . body = JSON . stringify ( requestArgument . value , JSONStringifyReplacer ) ;
107158 break ;
108159 case 'query' :
109160 query . push (
110161 encodeURIComponent ( requestArgument . name ) + '=' + encodeURIComponent ( requestArgument . value )
111162 ) ;
112163 break ;
113164 default :
165+ transport satisfies never ;
114166 throw new Error ( 'Unknown argument transport: ' + requestArgument . transport ) ;
115167 }
116168 } ) ;
117169
118170 if ( query . length > 0 ) {
119- url += '?' + query . join ( '&' ) ;
171+ out . url += '?' + query . join ( '&' ) ;
172+ }
173+ return out ;
174+ }
175+
176+ }
177+
178+ export class RestClient {
179+ private static globalHeaders : { [ key : string ] : string } = { } ;
180+
181+ static setHeader ( name : string , value : string | undefined ) {
182+ if ( ! value ) {
183+ delete this . globalHeaders [ name ] ;
184+ return this ;
120185 }
186+ this . globalHeaders [ name ] = value ;
187+ return this ;
188+ }
121189
122- const result = await fetch ( url , opts ) ;
190+ static setAuthorization ( auth : string | undefined ) {
191+ return this . setHeader ( 'Authorization' , auth ) ;
192+ }
123193
124- if ( result . status === 404 ) {
125- return null ;
194+ static setBearerToken ( token : string | undefined ) {
195+ return this . setAuthorization ( token ? `Bearer ${ token } ` : token ) ;
196+ }
197+
198+ private readonly _baseUrl : string ;
199+ private _fixedHeaders : { [ key : string ] : string } = { } ;
200+
201+ /**
202+ * Initialise rest client
203+ */
204+ constructor ( baseUrl : string ) {
205+ if ( ! baseUrl ) {
206+ baseUrl = '/' ;
126207 }
127208
128- let output : ReturnData | null = null ;
129- if ( result . headers . get ( 'content-type' ) ?. startsWith ( 'application/json' ) ) {
130- //Only parse json if content-type is application/json
131- const text = await result . text ( ) ;
132- output = text ? ( JSON . parse ( text ) as ReturnData ) : null ;
209+ if ( ! baseUrl . endsWith ( '/' ) ) {
210+ baseUrl += '/' ;
133211 }
134212
135- if ( result . status >= 400 ) {
136- const error =
137- output && typeof output === 'object' && 'error' in output && typeof output . error === 'string'
138- ? output . error
139- : 'Unknown error' ;
140- throw new RestError ( error , result ) ;
213+ this . _baseUrl = baseUrl ;
214+ }
215+
216+ public get baseUrl ( ) {
217+ return this . _baseUrl ;
218+ }
219+
220+ public withHeader ( name : string , value : string | undefined ) {
221+ if ( ! value ) {
222+ delete this . _fixedHeaders [ name ] ;
223+ return this ;
141224 }
225+ this . _fixedHeaders [ name ] = value ;
226+ return this ;
227+ }
142228
143- return output ;
229+ public withContentType ( contentType : string | undefined ) {
230+ return this . withHeader ( 'Content-Type' , contentType ) ;
231+ }
232+
233+ public withAuthorization ( auth : string | undefined ) {
234+ return this . withHeader ( 'Authorization' , auth ) ;
235+ }
236+
237+ public withBearerToken ( token : string | undefined ) {
238+ return this . withAuthorization ( token ? `Bearer ${ token } ` : token ) ;
239+ }
240+
241+ protected afterCreate ( request : RestClientRequest ) :void {
242+ // Override this method to add additional headers or similar to all requests
243+ }
244+
245+ public create < ReturnType = any > ( method : RequestMethod , path : string , requestArguments : RequestArgument [ ] ) :RestClientRequest < ReturnType > {
246+ const request = new RestClientRequest < ReturnType > ( this . _baseUrl , method , path , requestArguments ) ;
247+
248+ Object . entries ( RestClient . globalHeaders ) . forEach ( ( [ key , value ] ) => {
249+ request . withHeader ( key , value ) ;
250+ } ) ;
251+
252+ Object . entries ( this . _fixedHeaders ) . forEach ( ( [ key , value ] ) => {
253+ request . withHeader ( key , value ) ;
254+ } ) ;
255+
256+ this . afterCreate ( request ) ;
257+ return request ;
258+ }
259+
260+ /**
261+ * Executes a request to the specified path using the specified method.
262+ */
263+ public execute < ReturnType = any > ( method : RequestMethod , path : string , requestArguments : RequestArgument [ ] ) {
264+ const request = this . create < ReturnType > ( method , path , requestArguments ) ;
265+
266+ return request . call ( )
144267 }
145268}
0 commit comments