@@ -33,12 +33,19 @@ use std::str::FromStr;
3333#[ derive( Clone ) ]
3434pub struct RestQuery < N : Network > {
3535 base_url : http:: Uri ,
36+ /// `true` if the api version is already contained in the base URL.
37+ has_api_version : bool ,
3638 _marker : std:: marker:: PhantomData < N > ,
3739}
3840
3941impl < N : Network > From < http:: Uri > for RestQuery < N > {
4042 fn from ( base_url : http:: Uri ) -> Self {
41- Self { base_url, _marker : Default :: default ( ) }
43+ // Avoid trailing slash when checking the version
44+ let path = base_url. path ( ) . strip_suffix ( '/' ) . unwrap_or ( base_url. path ( ) ) ;
45+
46+ let has_api_version = path. ends_with ( Self :: API_V1 ) || path. ends_with ( Self :: API_V2 ) ;
47+
48+ Self { base_url, has_api_version, _marker : Default :: default ( ) }
4249 }
4350}
4451
@@ -78,6 +85,7 @@ impl<N: Network> FromStr for RestQuery<N> {
7885 fn from_str ( str_representation : & str ) -> Result < Self > {
7986 let base_url = str_representation. parse :: < http:: Uri > ( ) . with_context ( || "Failed to parse URL" ) ?;
8087
88+ // Perform checks.
8189 if let Some ( scheme) = base_url. scheme ( )
8290 && * scheme != uri:: Scheme :: HTTP
8391 && * scheme != uri:: Scheme :: HTTPS
@@ -97,7 +105,7 @@ impl<N: Network> FromStr for RestQuery<N> {
97105 bail ! ( "Base URL for REST endpoints cannot contain a query" ) ;
98106 }
99107
100- Ok ( Self { base_url , _marker : Default :: default ( ) } )
108+ Ok ( Self :: from ( base_url ) )
101109 }
102110}
103111
@@ -153,8 +161,8 @@ impl<N: Network> QueryTrait<N> for RestQuery<N> {
153161}
154162
155163impl < N : Network > RestQuery < N > {
156- /// The API version to use when querying REST endpoints.
157- const API_VERSION : & str = "v2" ;
164+ const API_V1 : & str = "v1" ;
165+ const API_V2 : & str = "v2" ;
158166
159167 /// Returns the transaction for the given transaction ID.
160168 pub fn get_transaction ( & self , transaction_id : & N :: TransactionID ) -> Result < Transaction < N > > {
@@ -187,22 +195,15 @@ impl<N: Network> RestQuery<N> {
187195 // This function is only called internally but check for additional sanity.
188196 ensure ! ( !route. starts_with( '/' ) , "path cannot start with a slash" ) ;
189197
198+ // Add the API version if it is not already contained in the base URL.
199+ let api_version = if self . has_api_version { "" } else { "v2/" } ;
200+
190201 // Work around a bug in the `http` crate where empty paths will be set to '/' but other paths are not appended with a slash.
191202 // See [this issue](https://github.com/hyperium/http/issues/507).
192203 let path = if self . base_url . path ( ) . ends_with ( '/' ) {
193- format ! (
194- "{base_url}{api_version}/{network}/{route}" ,
195- base_url = self . base_url,
196- api_version = Self :: API_VERSION ,
197- network = N :: SHORT_NAME
198- )
204+ format ! ( "{base_url}{api_version}{network}/{route}" , base_url = self . base_url, network = N :: SHORT_NAME )
199205 } else {
200- format ! (
201- "{base_url}/{api_version}/{network}/{route}" ,
202- base_url = self . base_url,
203- api_version = Self :: API_VERSION ,
204- network = N :: SHORT_NAME
205- )
206+ format ! ( "{base_url}/{api_version}{network}/{route}" , base_url = self . base_url, network = N :: SHORT_NAME )
206207 } ;
207208
208209 Ok ( path)
@@ -225,10 +226,23 @@ impl<N: Network> RestQuery<N> {
225226 if response. status ( ) . is_success ( ) {
226227 response. body_mut ( ) . read_json ( ) . with_context ( || "Failed to parse JSON response" )
227228 } else {
229+ let content_type = response
230+ . headers ( )
231+ . get ( "Content-Type" )
232+ . ok_or_else ( || anyhow ! ( "Endpoint return error without ContentType" ) ) ?
233+ . to_str ( )
234+ . with_context ( || "Endpoint returned invalid ContentType" ) ?;
235+
228236 // Convert returned error into an `anyhow::Error`.
229- let error: RestError =
230- response. body_mut ( ) . read_json ( ) . with_context ( || "Failed to parse JSON error response" ) ?;
231- Err ( error. parse ( ) . context ( format ! ( "Failed to fetch from {endpoint}" ) ) )
237+ // Depending on the API version, the error is either encoded as a string or as a JSON.
238+ if content_type. contains ( "json" ) {
239+ let error: RestError =
240+ response. body_mut ( ) . read_json ( ) . with_context ( || "Failed to parse JSON error response" ) ?;
241+ Err ( error. parse ( ) . context ( format ! ( "Failed to fetch from {endpoint}" ) ) )
242+ } else {
243+ let error = response. body_mut ( ) . read_to_string ( ) . with_context ( || "Failed to read error message" ) ?;
244+ Err ( anyhow ! ( error) . context ( format ! ( "Failed to fetch from {endpoint}" ) ) )
245+ }
232246 }
233247 }
234248
@@ -304,6 +318,8 @@ mod tests {
304318 let base = "http://localhost:3030/a/prefix" ;
305319 let route = "a/route" ;
306320 let query = base. parse :: < CurrentQuery > ( ) . unwrap ( ) ;
321+ let Query :: REST ( rest_query) = & query else { panic ! ( ) } ;
322+ assert ! ( !rest_query. has_api_version) ;
307323
308324 // Test without trailing slash.
309325 let Query :: REST ( rest) = query else { panic ! ( ) } ;
@@ -316,4 +332,17 @@ mod tests {
316332
317333 Ok ( ( ) )
318334 }
335+
336+ #[ test]
337+ fn test_rest_url_parse_with_api_version ( ) {
338+ let base = "http://localhost:3030/a/prefix/v1" ;
339+ let query = base. parse :: < CurrentQuery > ( ) . unwrap ( ) ;
340+ let Query :: REST ( rest_query) = & query else { panic ! ( ) } ;
341+ assert ! ( rest_query. has_api_version) ;
342+
343+ let base = "http://localhost:3030/a/prefix/v2/" ;
344+ let query = base. parse :: < CurrentQuery > ( ) . unwrap ( ) ;
345+ let Query :: REST ( rest_query) = & query else { panic ! ( ) } ;
346+ assert ! ( rest_query. has_api_version) ;
347+ }
319348}
0 commit comments