@@ -46,6 +46,7 @@ pub struct Configuration {
4646 pub ( crate ) user_agent : String ,
4747 pub ( crate ) unstable_operations : HashMap < String , bool > ,
4848 pub ( crate ) auth_keys : HashMap < String , APIKey > ,
49+ pub ( crate ) pat : Option < String > ,
4950 pub server_index : usize ,
5051 pub server_variables : HashMap < String , String > ,
5152 pub server_operation_index : HashMap < String , usize > ,
@@ -109,6 +110,30 @@ impl Configuration {
109110 self . auth_keys . insert ( operation_str. to_string ( ) , api_key) ;
110111 }
111112
113+ /// Set a bearer token for authentication.
114+ /// When a bearer token is configured, the client sends only an `Authorization: Bearer <token>`
115+ /// header and does NOT send DD-API-KEY or DD-APPLICATION-KEY headers.
116+ pub fn set_pat ( & mut self , pat : String ) {
117+ self . pat = Some ( pat) ;
118+ }
119+
120+ /// Build authentication headers for an API request.
121+ /// If a bearer token is configured, returns a single `Authorization: Bearer` header.
122+ /// Otherwise, returns API key headers.
123+ pub fn auth_headers ( & self ) -> Vec < ( String , String ) > {
124+ if let Some ( ref pat) = self . pat {
125+ return vec ! [ ( "Authorization" . to_string( ) , format!( "Bearer {}" , pat) ) ] ;
126+ }
127+ let mut headers = Vec :: new ( ) ;
128+ if let Some ( key) = self . auth_keys . get ( "apiKeyAuth" ) {
129+ headers. push ( ( "DD-API-KEY" . to_string ( ) , key. key . clone ( ) ) ) ;
130+ }
131+ if let Some ( key) = self . auth_keys . get ( "appKeyAuth" ) {
132+ headers. push ( ( "DD-APPLICATION-KEY" . to_string ( ) , key. key . clone ( ) ) ) ;
133+ }
134+ headers
135+ }
136+
112137 pub fn set_proxy_url ( & mut self , proxy_url : Option < String > ) {
113138 self . proxy_url = proxy_url;
114139 }
@@ -363,10 +388,14 @@ impl Default for Configuration {
363388 } ,
364389 ) ;
365390
391+ // DD_BEARER_TOKEN env var enables Bearer token auth (mutually exclusive with API key auth)
392+ let pat = env:: var ( "DD_BEARER_TOKEN" ) . ok ( ) . filter ( |p| !p. is_empty ( ) ) ;
393+
366394 Self {
367395 user_agent : DEFAULT_USER_AGENT . clone ( ) ,
368396 unstable_operations,
369397 auth_keys,
398+ pat,
370399 server_index : 0 ,
371400 server_variables : HashMap :: from ( [ (
372401 "site" . into ( ) ,
@@ -1128,3 +1157,138 @@ lazy_static! {
11281157 ] )
11291158 } ;
11301159}
1160+
1161+ #[ cfg( test) ]
1162+ mod tests {
1163+ use super :: * ;
1164+ use std:: sync:: Mutex ;
1165+
1166+ // Mutex to prevent env var tests from interfering with each other
1167+ static ENV_MUTEX : Mutex < ( ) > = Mutex :: new ( ( ) ) ;
1168+
1169+ #[ test]
1170+ fn test_set_pat_stores_pat ( ) {
1171+ let mut config = Configuration :: new ( ) ;
1172+ config. set_pat ( "my-pat-token" . to_string ( ) ) ;
1173+ assert_eq ! ( config. pat. as_deref( ) , Some ( "my-pat-token" ) ) ;
1174+ }
1175+
1176+ #[ test]
1177+ fn test_auth_headers_with_pat_returns_bearer ( ) {
1178+ let mut config = Configuration :: new ( ) ;
1179+ config. set_pat ( "my-pat-token" . to_string ( ) ) ;
1180+
1181+ let headers = config. auth_headers ( ) ;
1182+ assert_eq ! ( headers. len( ) , 1 ) ;
1183+ assert_eq ! ( headers[ 0 ] . 0 , "Authorization" ) ;
1184+ assert_eq ! ( headers[ 0 ] . 1 , "Bearer my-pat-token" ) ;
1185+ }
1186+
1187+ #[ test]
1188+ fn test_auth_headers_with_pat_excludes_api_keys ( ) {
1189+ let mut config = Configuration :: new ( ) ;
1190+ config. set_auth_key (
1191+ "apiKeyAuth" ,
1192+ APIKey {
1193+ key : "my-api-key" . to_string ( ) ,
1194+ prefix : "" . to_string ( ) ,
1195+ } ,
1196+ ) ;
1197+ config. set_auth_key (
1198+ "appKeyAuth" ,
1199+ APIKey {
1200+ key : "my-app-key" . to_string ( ) ,
1201+ prefix : "" . to_string ( ) ,
1202+ } ,
1203+ ) ;
1204+ // PAT overrides all key-based auth
1205+ config. set_pat ( "my-pat-token" . to_string ( ) ) ;
1206+
1207+ let headers = config. auth_headers ( ) ;
1208+ assert_eq ! ( headers. len( ) , 1 ) ;
1209+ assert_eq ! ( headers[ 0 ] . 0 , "Authorization" ) ;
1210+ assert_eq ! ( headers[ 0 ] . 1 , "Bearer my-pat-token" ) ;
1211+ }
1212+
1213+ #[ test]
1214+ fn test_auth_headers_without_pat_returns_api_keys ( ) {
1215+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
1216+ let old_pat = env:: var ( "DD_BEARER_TOKEN" ) . ok ( ) ;
1217+ env:: remove_var ( "DD_BEARER_TOKEN" ) ;
1218+
1219+ let mut config = Configuration :: new ( ) ;
1220+ config. set_auth_key (
1221+ "apiKeyAuth" ,
1222+ APIKey {
1223+ key : "my-api-key" . to_string ( ) ,
1224+ prefix : "" . to_string ( ) ,
1225+ } ,
1226+ ) ;
1227+ config. set_auth_key (
1228+ "appKeyAuth" ,
1229+ APIKey {
1230+ key : "my-app-key" . to_string ( ) ,
1231+ prefix : "" . to_string ( ) ,
1232+ } ,
1233+ ) ;
1234+
1235+ let headers = config. auth_headers ( ) ;
1236+ assert ! ( headers. iter( ) . any( |( k, v) | k == "DD-API-KEY" && v == "my-api-key" ) ) ;
1237+ assert ! ( headers. iter( ) . any( |( k, v) | k == "DD-APPLICATION-KEY" && v == "my-app-key" ) ) ;
1238+ // No Authorization header should be present
1239+ assert ! ( !headers. iter( ) . any( |( k, _) | k == "Authorization" ) ) ;
1240+
1241+ match old_pat {
1242+ Some ( v) => env:: set_var ( "DD_BEARER_TOKEN" , v) ,
1243+ None => env:: remove_var ( "DD_BEARER_TOKEN" ) ,
1244+ }
1245+ }
1246+
1247+ #[ test]
1248+ fn test_dd_pat_env_var ( ) {
1249+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
1250+ let old_pat = env:: var ( "DD_BEARER_TOKEN" ) . ok ( ) ;
1251+
1252+ env:: set_var ( "DD_BEARER_TOKEN" , "env-pat-token" ) ;
1253+
1254+ let config = Configuration :: default ( ) ;
1255+ assert_eq ! ( config. pat. as_deref( ) , Some ( "env-pat-token" ) ) ;
1256+
1257+ let headers = config. auth_headers ( ) ;
1258+ assert_eq ! ( headers. len( ) , 1 ) ;
1259+ assert_eq ! ( headers[ 0 ] . 0 , "Authorization" ) ;
1260+ assert_eq ! ( headers[ 0 ] . 1 , "Bearer env-pat-token" ) ;
1261+
1262+ // Restore env
1263+ match old_pat {
1264+ Some ( v) => env:: set_var ( "DD_BEARER_TOKEN" , v) ,
1265+ None => env:: remove_var ( "DD_BEARER_TOKEN" ) ,
1266+ }
1267+ }
1268+
1269+ #[ test]
1270+ fn test_empty_dd_pat_does_not_set_pat ( ) {
1271+ let _lock = ENV_MUTEX . lock ( ) . unwrap ( ) ;
1272+ let old_pat = env:: var ( "DD_BEARER_TOKEN" ) . ok ( ) ;
1273+ let old_app_key = env:: var ( "DD_APP_KEY" ) . ok ( ) ;
1274+
1275+ env:: set_var ( "DD_BEARER_TOKEN" , "" ) ;
1276+ env:: set_var ( "DD_APP_KEY" , "my-app-key" ) ;
1277+
1278+ let config = Configuration :: default ( ) ;
1279+ assert ! ( config. pat. is_none( ) ) ;
1280+
1281+ let headers = config. auth_headers ( ) ;
1282+ assert ! ( headers. iter( ) . any( |( k, v) | k == "DD-APPLICATION-KEY" && v == "my-app-key" ) ) ;
1283+
1284+ // Restore env
1285+ match old_pat {
1286+ Some ( v) => env:: set_var ( "DD_BEARER_TOKEN" , v) ,
1287+ None => env:: remove_var ( "DD_BEARER_TOKEN" ) ,
1288+ }
1289+ match old_app_key {
1290+ Some ( v) => env:: set_var ( "DD_APP_KEY" , v) ,
1291+ None => env:: remove_var ( "DD_APP_KEY" ) ,
1292+ }
1293+ }
1294+ }
0 commit comments