@@ -252,6 +252,8 @@ pub enum ApiErrorKind {
252252 RequestFailed ,
253253 #[ error( "could not compress data" ) ]
254254 CompressionFailed ,
255+ #[ error( "region overrides cannot be applied to absolute urls" ) ]
256+ InvalidRegionRequest ,
255257}
256258
257259#[ derive( Debug , thiserror:: Error ) ]
@@ -391,28 +393,65 @@ impl Api {
391393 /// URL is just a path then it's relative to the configured API host
392394 /// and authentication is automatically enabled.
393395 pub fn request ( & self , method : Method , url : & str ) -> ApiResult < ApiRequest > {
394- let mut handle = self . pool . get ( ) . unwrap ( ) ;
395- handle. reset ( ) ;
396- if !self . config . allow_keepalive ( ) {
397- handle. forbid_reuse ( true ) . ok ( ) ;
398- }
399- let mut ssl_opts = curl:: easy:: SslOpt :: new ( ) ;
400- if self . config . disable_ssl_revocation_check ( ) {
401- ssl_opts. no_revoke ( true ) ;
396+ let ( url, auth) = self . resolve_base_url_and_auth ( url, None ) ?;
397+ self . construct_api_request ( method, & url, auth)
398+ }
399+
400+ /// Like `request`, but constructs a new `ApiRequest` using the base URL
401+ /// plus a region prefix for requests that must be routed to a region.
402+ pub fn region_request (
403+ & self ,
404+ method : Method ,
405+ url : & str ,
406+ region : & Region ,
407+ ) -> ApiResult < ApiRequest > {
408+ let ( url, auth) = self . resolve_base_url_and_auth ( url, Some ( region) ) ?;
409+ self . construct_api_request ( method, & url, auth)
410+ }
411+
412+ fn resolve_base_url_and_auth (
413+ & self ,
414+ url : & str ,
415+ region : Option < & Region > ,
416+ ) -> ApiResult < ( String , Option < & Auth > ) > {
417+ if is_absolute_url ( url) && region. is_some ( ) {
418+ return Err ( ApiErrorKind :: InvalidRegionRequest . into ( ) ) ;
402419 }
403- handle . ssl_options ( & ssl_opts ) ? ;
420+
404421 let ( url, auth) = if is_absolute_url ( url) {
405422 ( Cow :: Borrowed ( url) , None )
406423 } else {
424+ let host_override = region. map ( |rg| rg. url . as_str ( ) ) ;
425+
407426 (
408- Cow :: Owned ( match self . config . get_api_endpoint ( url) {
427+ Cow :: Owned ( match self . config . get_api_endpoint ( url, host_override ) {
409428 Ok ( rv) => rv,
410429 Err ( err) => return Err ( ApiError :: with_source ( ApiErrorKind :: BadApiUrl , err) ) ,
411430 } ) ,
412431 self . config . get_auth ( ) ,
413432 )
414433 } ;
415434
435+ Ok ( ( url. into_owned ( ) , auth) )
436+ }
437+
438+ fn construct_api_request (
439+ & self ,
440+ method : Method ,
441+ url : & str ,
442+ auth : Option < & Auth > ,
443+ ) -> ApiResult < ApiRequest > {
444+ let mut handle = self . pool . get ( ) . unwrap ( ) ;
445+ handle. reset ( ) ;
446+ if !self . config . allow_keepalive ( ) {
447+ handle. forbid_reuse ( true ) . ok ( ) ;
448+ }
449+ let mut ssl_opts = curl:: easy:: SslOpt :: new ( ) ;
450+ if self . config . disable_ssl_revocation_check ( ) {
451+ ssl_opts. no_revoke ( true ) ;
452+ }
453+ handle. ssl_options ( & ssl_opts) ?;
454+
416455 if let Some ( proxy_url) = self . config . get_proxy_url ( ) {
417456 handle. proxy ( & proxy_url) ?;
418457 }
@@ -431,7 +470,7 @@ impl Api {
431470 let env = self . config . get_pipeline_env ( ) ;
432471 let headers = self . config . get_headers ( ) ;
433472
434- ApiRequest :: create ( handle, & method, & url, auth, env, headers)
473+ ApiRequest :: create ( handle, & method, url, auth, env, headers)
435474 }
436475
437476 /// Convenience method that performs a request using DSN as authentication method.
@@ -445,7 +484,7 @@ impl Api {
445484 // We resolve an absolute URL to skip default authentication flow.
446485 let url = self
447486 . config
448- . get_api_endpoint ( url)
487+ . get_api_endpoint ( url, None )
449488 . map_err ( |err| ApiError :: with_source ( ApiErrorKind :: BadApiUrl , err) ) ?;
450489
451490 let mut request = self
@@ -1443,11 +1482,19 @@ impl Api {
14431482 }
14441483
14451484 /// List all organizations associated with the authenticated token
1446- pub fn list_organizations ( & self ) -> ApiResult < Vec < Organization > > {
1485+ /// in the given `Region`. If no `Region` is provided, we assume
1486+ /// we're issuing a request to a monolith deployment.
1487+ pub fn list_organizations ( & self , region : Option < & Region > ) -> ApiResult < Vec < Organization > > {
14471488 let mut rv = vec ! [ ] ;
14481489 let mut cursor = "" . to_string ( ) ;
14491490 loop {
1450- let resp = self . get ( & format ! ( "/organizations/?cursor={}" , QueryArg ( & cursor) ) ) ?;
1491+ let current_path = & format ! ( "/organizations/?cursor={}" , QueryArg ( & cursor) ) ;
1492+ let resp = if let Some ( rg) = region {
1493+ self . region_request ( Method :: Get , current_path, rg) ?. send ( ) ?
1494+ } else {
1495+ self . get ( current_path) ?
1496+ } ;
1497+
14511498 if resp. status ( ) == 404 || ( resp. status ( ) == 400 && !cursor. is_empty ( ) ) {
14521499 if rv. is_empty ( ) {
14531500 return Err ( ApiErrorKind :: ResourceNotFound . into ( ) ) ;
@@ -1466,6 +1513,22 @@ impl Api {
14661513 Ok ( rv)
14671514 }
14681515
1516+ pub fn list_available_regions ( & self ) -> ApiResult < Vec < Region > > {
1517+ let resp = self . get ( "/users/me/regions/" ) ?;
1518+ if resp. status ( ) == 404 {
1519+ // This endpoint may not exist for self-hosted users, so
1520+ // returning a default of [] seems appropriate.
1521+ return Ok ( vec ! [ ] ) ;
1522+ }
1523+
1524+ if resp. status ( ) == 400 {
1525+ return Err ( ApiErrorKind :: ResourceNotFound . into ( ) ) ;
1526+ }
1527+
1528+ let region_response = resp. convert :: < RegionResponse > ( ) ?;
1529+ Ok ( region_response. regions )
1530+ }
1531+
14691532 /// List all monitors associated with an organization
14701533 pub fn list_organization_monitors ( & self , org : & str ) -> ApiResult < Vec < Monitor > > {
14711534 let mut rv = vec ! [ ] ;
@@ -2947,3 +3010,14 @@ impl fmt::Display for ProcessedEventTag {
29473010 Ok ( ( ) )
29483011 }
29493012}
3013+
3014+ #[ derive( Clone , Debug , Deserialize ) ]
3015+ pub struct Region {
3016+ pub name : String ,
3017+ pub url : String ,
3018+ }
3019+
3020+ #[ derive( Clone , Debug , Deserialize ) ]
3021+ pub struct RegionResponse {
3022+ pub regions : Vec < Region > ,
3023+ }
0 commit comments