Skip to content

Commit 8606c34

Browse files
authored
feat: add User-Agent header to all outbound HTTP requests (#37)
Add explicit User-Agent header to all HTTP requests made by the SDK. The header follows the format: flagsmith-rust-sdk/<version> Implementation details: - Created get_user_agent() function that reads version from CARGO_PKG_VERSION at compile time using option_env! macro - Falls back to "flagsmith-rust-sdk/unknown" if version unavailable - User-Agent header is added to default headers in Flagsmith client constructor - Header is automatically included in both API requests and analytics calls Testing: - Added test_get_user_agent_format() to verify the function returns correct format and actual version (not "unknown") during cargo test - Added test_user_agent_header_is_set() to verify the header is sent in HTTP requests using httpmock
1 parent 288364a commit 8606c34

File tree

1 file changed

+66
-0
lines changed

1 file changed

+66
-0
lines changed

src/flagsmith/mod.rs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ pub mod offline_handler;
2323

2424
const DEFAULT_API_URL: &str = "https://edge.api.flagsmith.com/api/v1/";
2525

26+
// Get the SDK version from Cargo.toml at compile time, or default to "unknown"
27+
fn get_user_agent() -> String {
28+
let version = option_env!("CARGO_PKG_VERSION").unwrap_or("unknown");
29+
format!("flagsmith-rust-sdk/{}", version)
30+
}
31+
2632
pub struct FlagsmithOptions {
2733
pub api_url: String,
2834
pub custom_headers: HeaderMap,
@@ -75,6 +81,10 @@ impl Flagsmith {
7581
header::HeaderValue::from_str(&environment_key).unwrap(),
7682
);
7783
headers.insert("Content-Type", "application/json".parse().unwrap());
84+
headers.insert(
85+
header::USER_AGENT,
86+
header::HeaderValue::from_str(&get_user_agent()).unwrap(),
87+
);
7888
let timeout = Duration::from_secs(flagsmith_options.request_timeout_seconds);
7989
let client = reqwest::blocking::Client::builder()
8090
.default_headers(headers.clone())
@@ -510,6 +520,28 @@ mod tests {
510520
implements_send_and_sync::<Flagsmith>();
511521
}
512522

523+
#[test]
524+
fn test_get_user_agent_format() {
525+
// When
526+
let user_agent = get_user_agent();
527+
528+
// Then
529+
assert!(user_agent.starts_with("flagsmith-rust-sdk/"));
530+
531+
// Extract version part after the slash
532+
let version = user_agent.strip_prefix("flagsmith-rust-sdk/").unwrap();
533+
534+
// During cargo test, CARGO_PKG_VERSION is always set, so we should never get "unknown"
535+
assert_ne!(version, "unknown", "Version should not be 'unknown' during cargo test");
536+
537+
// Version should contain numbers (semantic versioning: e.g., "2.0.0")
538+
assert!(
539+
version.chars().any(|c| c.is_numeric()),
540+
"Version should contain numbers, got: {}",
541+
version
542+
);
543+
}
544+
513545
#[test]
514546
fn polling_thread_updates_environment_on_start() {
515547
// Given
@@ -603,4 +635,38 @@ mod tests {
603635
assert_eq!(flags.unwrap().get_feature_value_as_string("some_feature").unwrap().to_owned(), "some-value");
604636
assert_eq!(identity_flags.unwrap().get_feature_value_as_string("some_feature").unwrap().to_owned(), "some-overridden-value");
605637
}
638+
639+
#[test]
640+
fn test_user_agent_header_is_set() {
641+
// Given
642+
let environment_key = "ser.test_environment_key";
643+
let response_body: serde_json::Value = serde_json::from_str(ENVIRONMENT_JSON).unwrap();
644+
let expected_user_agent = get_user_agent();
645+
646+
let mock_server = MockServer::start();
647+
let api_mock = mock_server.mock(|when, then| {
648+
when.method(GET)
649+
.path("/api/v1/environment-document/")
650+
.header("X-Environment-Key", environment_key)
651+
.header("User-Agent", expected_user_agent.as_str());
652+
then.status(200).json_body(response_body);
653+
});
654+
655+
let url = mock_server.url("/api/v1/");
656+
657+
let flagsmith_options = FlagsmithOptions {
658+
api_url: url,
659+
enable_local_evaluation: true,
660+
..Default::default()
661+
};
662+
663+
// When
664+
let _flagsmith = Flagsmith::new(environment_key.to_string(), flagsmith_options);
665+
666+
// let's wait for the thread to make the request
667+
thread::sleep(std::time::Duration::from_millis(50));
668+
669+
// Then
670+
api_mock.assert();
671+
}
606672
}

0 commit comments

Comments
 (0)