Skip to content

Commit 0b37c0d

Browse files
authored
Omit fields when deriving SafeDebug by default (#2516)
* Omit fields when deriving SafeDebug by default Resolves #1707. Callers can enable the `debug` feature of `typespec_client_core` or `azure_core` to emit normal `core::fmt::Debug` formatting. * Add/support more test cases * Refactor to support future `safe` attribute helper * Use older versions of some deps for 1.80 * Use `DebugTuple::finish()` on 1.80 `DebugTuple::finish_non_exhaustive()` wasn't added till 1.82. * Remove `SafeDebug` trait A derive macro doesn't need the trait, nor do we have need for one currently. * Add "msrv" to cspell dictionary words
1 parent b1ccf9c commit 0b37c0d

File tree

20 files changed

+2276
-30
lines changed

20 files changed

+2276
-30
lines changed

.vscode/cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"maxresults",
5252
"maxsize",
5353
"msrc",
54+
"msrv",
5455
"newtonsoft",
5556
"oidc",
5657
"pageable",

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sdk/core/azure_core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
### Other Changes
1212

13+
- Deriving `SafeDebug` formats non-exhaustive types by default. Enable `debug` feature to format normal `Debug` output.
14+
1315
## 0.23.0 (2025-04-08)
1416

1517
### Features Added

sdk/core/azure_core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ default = [
4848
"reqwest_gzip",
4949
]
5050
azurite_workaround = []
51+
debug = ["typespec_client_core/debug"]
5152
hmac_openssl = ["dep:openssl"]
5253
hmac_rust = ["dep:sha2", "dep:hmac"]
5354
reqwest = ["typespec_client_core/reqwest"]

sdk/core/azure_core/README.md

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
7171
};
7272

7373
let client = SecretClient::new(
74-
"https://your-key-vault-name.vault.azure.net/",
74+
"https://<your-key-vault-name>.vault.azure.net/",
7575
credential.clone(),
7676
Some(options),
7777
)?;
@@ -96,7 +96,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9696
// create a client
9797
let credential = DefaultAzureCredential::new()?;
9898
let client = SecretClient::new(
99-
"https://your-key-vault-name.vault.azure.net/",
99+
"https://<your-key-vault-name>.vault.azure.net/",
100100
credential.clone(),
101101
None,
102102
)?;
@@ -140,7 +140,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
140140
// create a client
141141
let credential = DefaultAzureCredential::new()?;
142142
let client = SecretClient::new(
143-
"https://your-key-vault-name.vault.azure.net/",
143+
"https://<your-key-vault-name>.vault.azure.net/",
144144
credential.clone(),
145145
None,
146146
)?;
@@ -178,7 +178,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
178178
// create a client
179179
let credential = DefaultAzureCredential::new()?;
180180
let client = SecretClient::new(
181-
"https://your-key-vault-name.vault.azure.net/",
181+
"https://<your-key-vault-name>.vault.azure.net/",
182182
credential.clone(),
183183
None,
184184
)?;
@@ -201,7 +201,49 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
201201
}
202202
```
203203

204-
<!-- ## Troubleshooting -->
204+
## Troubleshooting
205+
206+
### Logging
207+
208+
To help protected end users from accidental Personally-Identifiable Information (PII) from leaking into logs or traces, models' default implementation of `core::fmt::Debug` formats as non-exhaustive structure tuple e.g.,
209+
210+
```rust no_run
211+
use azure_identity::DefaultAzureCredential;
212+
use azure_security_keyvault_secrets::{ResourceExt, SecretClient};
213+
214+
#[tokio::main]
215+
async fn main() -> Result<(), Box<dyn std::error::Error>> {
216+
// create a client
217+
let credential = DefaultAzureCredential::new()?;
218+
let client = SecretClient::new(
219+
"https://<your-key-vault-name>.vault.azure.net/",
220+
credential.clone(),
221+
None,
222+
)?;
223+
224+
// get a secret
225+
let secret = client.get_secret("secret-name", "", None)
226+
.await?
227+
.into_body()
228+
.await?;
229+
230+
println!("{secret:#?}");
231+
232+
Ok(())
233+
}
234+
```
235+
236+
By default this will print:
237+
238+
```text
239+
Secret { .. }
240+
```
241+
242+
Though not recommended for production, you can enable normal `core::fmt::Debug` formatting complete with field names and values by enabling the `debug` feature of `azure_core` e.g.,
243+
244+
```sh
245+
cargo add azure_core -F debug
246+
```
205247

206248
## Contributing
207249

sdk/typespec/typespec_client_core/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
### Other Changes
1212

13+
- Deriving `SafeDebug` formats non-exhaustive types by default. Enable `debug` feature to format normal `Debug` output.
14+
1315
## 0.2.0 (2025-04-08)
1416

1517
### Breaking Changes

sdk/typespec/typespec_client_core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ typespec_macros.path = "../typespec_macros"
4545

4646
[features]
4747
default = ["http", "json", "reqwest", "reqwest_deflate", "reqwest_gzip"]
48+
debug = ["typespec_macros?/debug"]
4849
derive = ["dep:typespec_macros"]
4950
http = ["typespec/http"]
5051
json = ["typespec/json"]

sdk/typespec/typespec_client_core/src/fmt.rs

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,18 @@
33

44
//! Formatting helpers.
55
6-
use std::{borrow::Cow, fmt::Debug};
6+
use std::borrow::Cow;
77

8-
#[cfg(feature = "derive")]
9-
pub use typespec_macros::SafeDebug;
10-
11-
/// When deriving this trait, helps prevent leaking personally identifiable information (PII) that deriving [`Debug`] might otherwise.
8+
/// Derive to help prevent leaking personally identifiable information (PII) that deriving [`Debug`](std::fmt::Debug) might otherwise.
9+
///
10+
/// `SafeDebug` is not a trait and cannot be implemented, nor should you derive `Debug` explicitly.
11+
/// Only when you derive `SafeDebug` will types help prevent leaking PII because, by default, only the type name is printed.
12+
/// Only when you import `typespec_client_core` with feature `debug` will it derive `Debug` normally.
1213
///
1314
/// # Examples
1415
///
1516
/// ```
16-
/// use typespec_macros::SafeDebug;
17+
/// use typespec_client_core::fmt::SafeDebug;
1718
///
1819
/// #[derive(SafeDebug)]
1920
/// struct MyModel {
@@ -23,9 +24,14 @@ pub use typespec_macros::SafeDebug;
2324
/// let model = MyModel {
2425
/// name: Some("Kelly Smith".to_string()),
2526
/// };
26-
/// assert_eq!(format!("{model:?}"), "MyModel { .. }");
27+
/// if cfg!(feature = "debug") {
28+
/// assert_eq!(format!("{model:?}"), r#"MyModel { name: Some("Kelly Smith") }"#);
29+
/// } else {
30+
/// assert_eq!(format!("{model:?}"), "MyModel { .. }");
31+
/// }
2732
/// ```
28-
pub trait SafeDebug: Debug {}
33+
#[cfg(feature = "derive")]
34+
pub use typespec_macros::SafeDebug;
2935

3036
/// Converts ASCII characters in `value` to lowercase if required; otherwise, returns the original slice.
3137
///

sdk/typespec/typespec_macros/CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010

1111
### Other Changes
1212

13+
- Deriving `SafeDebug` formats non-exhaustive types by default. Enable `debug` feature to format normal `Debug` output.
14+
1315
## 0.2.0 (2025-04-08)
1416

1517
### Other Changes

sdk/typespec/typespec_macros/Cargo.toml

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,25 @@ keywords = ["typespec"]
1313
[lib]
1414
proc-macro = true
1515

16+
[features]
17+
debug = []
18+
1619
[dependencies]
17-
syn.workspace = true
18-
quote.workspace = true
1920
proc-macro2.workspace = true
21+
quote.workspace = true
22+
rustc_version.workspace = true
23+
syn.workspace = true
2024

2125
[dev-dependencies]
26+
cargo_metadata.workspace = true
27+
serde.workspace = true
28+
serde_json.workspace = true
2229
tokio.workspace = true
2330
typespec_client_core = { path = "../typespec_client_core", features = [
2431
"http",
2532
"json",
2633
"xml",
2734
] }
28-
serde.workspace = true
29-
serde_json.workspace = true
30-
cargo_metadata.workspace = true
35+
36+
[package.metadata.docs.rs]
37+
features = []

sdk/typespec/typespec_macros/src/lib.rs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,10 +85,11 @@ pub fn derive_model(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
8585
run_derive_macro(input, model::derive_model_impl)
8686
}
8787

88-
/// Derive macro for implementing the `SafeDebug` trait.
88+
/// Derive to help prevent leaking personally identifiable information (PII) that deriving [`Debug`](std::fmt::Debug) might otherwise.
8989
///
90-
/// Deriving this trait will derive a [`std::fmt::Debug`] implementation that should not leak personally identifiable information (PII).
91-
/// By default, only the structure or enumeration name will be returned.
90+
/// `SafeDebug` is not a trait and cannot be implemented, nor should you derive `Debug` explicitly.
91+
/// Only when you derive `SafeDebug` will types help prevent leaking PII because, by default, only the type name is printed.
92+
/// Only when you enable the `debug` feature will it derive `Debug` normally.
9293
///
9394
/// # Examples
9495
///
@@ -102,7 +103,11 @@ pub fn derive_model(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
102103
/// let model = MyModel {
103104
/// name: Some("Kelly Smith".to_string()),
104105
/// };
105-
/// assert_eq!(format!("{model:?}"), "MyModel { .. }");
106+
/// if cfg!(feature = "debug") {
107+
/// assert_eq!(format!("{model:?}"), r#"MyModel { name: Some("Kelly Smith") }"#);
108+
/// } else {
109+
/// assert_eq!(format!("{model:?}"), "MyModel { .. }");
110+
/// }
106111
/// ```
107112
#[proc_macro_derive(SafeDebug)]
108113
pub fn derive_safe_debug(input: proc_macro::TokenStream) -> proc_macro::TokenStream {

0 commit comments

Comments
 (0)