azure_core/policies/
telemetry_policy.rs

1use crate::headers::{HeaderValue, USER_AGENT};
2use crate::options::TelemetryOptions;
3use crate::policies::{Policy, PolicyResult};
4use crate::{Context, Request};
5use std::env::consts::{ARCH, OS};
6use std::sync::Arc;
7
8#[derive(Clone, Debug)]
9pub struct TelemetryPolicy {
10    header: String,
11}
12
13/// Sets the User-Agent header with useful information in a typical format for Azure SDKs.
14impl<'a> TelemetryPolicy {
15    pub fn new(
16        crate_name: Option<&'a str>,
17        crate_version: Option<&'a str>,
18        options: &TelemetryOptions,
19    ) -> Self {
20        Self::new_with_rustc_version(
21            crate_name,
22            crate_version,
23            option_env!("AZSDK_RUSTC_VERSION"),
24            options,
25        )
26    }
27
28    fn new_with_rustc_version(
29        crate_name: Option<&'a str>,
30        crate_version: Option<&'a str>,
31        rustc_version: Option<&'a str>,
32        options: &TelemetryOptions,
33    ) -> Self {
34        const UNKNOWN: &str = "unknown";
35        let mut crate_name = crate_name.unwrap_or(UNKNOWN);
36        let crate_version = crate_version.unwrap_or(UNKNOWN);
37        let rustc_version = rustc_version.unwrap_or(UNKNOWN);
38        let platform_info = format!("({rustc_version}; {OS}; {ARCH})",);
39
40        if let Some(name) = crate_name.strip_prefix("azure_") {
41            crate_name = name;
42        }
43
44        let header = match &options.application_id {
45            Some(application_id) => {
46                format!("{application_id} azsdk-rust-{crate_name}/{crate_version} {platform_info}")
47            }
48            None => format!("azsdk-rust-{crate_name}/{crate_version} {platform_info}"),
49        };
50
51        TelemetryPolicy { header }
52    }
53}
54
55#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
56#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
57impl Policy for TelemetryPolicy {
58    async fn send(
59        &self,
60        ctx: &Context,
61        request: &mut Request,
62        next: &[Arc<dyn Policy>],
63    ) -> PolicyResult {
64        request.insert_header(USER_AGENT, HeaderValue::from(self.header.to_string()));
65
66        next[0].send(ctx, request, &next[1..]).await
67    }
68}
69
70#[cfg(test)]
71mod test {
72    use super::*;
73
74    #[test]
75    fn test_without_application_id() {
76        let policy = TelemetryPolicy::new_with_rustc_version(
77            Some("azure_test"), // Tests that "azure_" is removed.
78            Some("1.2.3"),
79            Some("4.5.6"),
80            &TelemetryOptions::default(),
81        );
82        assert_eq!(
83            policy.header,
84            format!("azsdk-rust-test/1.2.3 (4.5.6; {OS}; {ARCH})")
85        );
86    }
87
88    #[test]
89    fn test_with_application_id() {
90        let options = TelemetryOptions {
91            application_id: Some("my_app".to_string()),
92        };
93        let policy = TelemetryPolicy::new_with_rustc_version(
94            Some("test"),
95            Some("1.2.3"),
96            Some("4.5.6"),
97            &options,
98        );
99        assert_eq!(
100            policy.header,
101            format!("my_app azsdk-rust-test/1.2.3 (4.5.6; {OS}; {ARCH})")
102        );
103    }
104
105    #[test]
106    fn test_missing_env() {
107        // Would simulate if option_env!("CARGO_PKG_NAME"), for example, returned None.
108        let policy =
109            TelemetryPolicy::new_with_rustc_version(None, None, None, &TelemetryOptions::default());
110        assert_eq!(
111            policy.header,
112            format!("azsdk-rust-unknown/unknown (unknown; {OS}; {ARCH})")
113        );
114    }
115}