qcs_api_client_common/configuration/
secret_string.rs1#![allow(
6 non_local_definitions,
7 unreachable_pub,
8 dead_code,
9 reason = "necessary for pyo3::pymethods"
10)]
11
12use serde::{Deserialize, Serialize};
13use std::{borrow::Cow, fmt};
14
15#[cfg(feature = "python")]
16use crate::{impl_eq, impl_repr};
17
18macro_rules! make_secret_string {
21 (
22 $(#[$attr:meta])*
23 $name:ident
24 ) => {
25 $(#[$attr])*
26 #[cfg_attr(feature = "python", ::pyo3::pyclass)]
27 #[derive(Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
28 #[serde(transparent)]
29 pub struct $name(Cow<'static, str>);
30
31 impl fmt::Debug for $name {
32 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
33 const NAME: &str = stringify!($name);
34 let len = self.0.len();
35 write!(f, "{NAME}(<REDACTED len: {len}>)")
36 }
37 }
38
39 impl<T: Into<Cow<'static, str>>> From<T> for $name {
40 fn from(value: T) -> Self {
41 Self(value.into())
42 }
43 }
44
45 impl $name {
46 #[must_use]
47 pub fn is_empty(&self) -> bool {
49 self.0.is_empty()
50 }
51
52 #[must_use]
53 pub fn secret(&self) -> &str {
55 self.0.as_ref()
56 }
57
58 }
59
60 #[cfg(feature = "python")]
61 impl_repr!($name);
62
63 #[cfg(feature = "python")]
64 impl_eq!($name);
65
66 #[cfg(feature = "python")]
67 #[::pyo3::pymethods]
68 impl $name {
69 #[new]
70 fn py_new(value: String) -> Self {
71 Self::from(value)
72 }
73
74 #[must_use]
75 #[getter]
76 #[pyo3(name = "is_empty")]
77 pub fn py_is_empty(&self) -> bool {
79 self.is_empty()
80 }
81
82 #[must_use]
83 #[getter]
84 #[pyo3(name = "secret")]
85 pub fn py_secret(&self) -> &str {
87 self.secret()
88 }
89 }
90 }
91}
92
93make_secret_string!(
94 SecretRefreshToken
96);
97
98make_secret_string!(
99 SecretAccessToken
101);
102
103make_secret_string!(
104 ClientSecret
106);
107
108#[cfg(test)]
109mod test {
110 use super::*;
111
112 make_secret_string!(TestSecret);
113
114 #[test]
115 fn test_secret_string_serialization() {
116 const SECRET_VALUE: &str = "my_secret_value";
117 const SECRET_VALUE_JSON: &str = "\"my_secret_value\"";
118
119 assert_eq!(
121 serde_json::to_value(TestSecret::from(SECRET_VALUE)).unwrap(),
122 serde_json::Value::String(SECRET_VALUE.to_string()),
123 );
124
125 let test_secret: TestSecret = serde_json::from_str(SECRET_VALUE_JSON).unwrap();
126 assert_eq!(test_secret.secret(), SECRET_VALUE);
127
128 assert_eq!(
129 serde_json::to_string(&test_secret).unwrap(),
130 SECRET_VALUE_JSON
131 );
132 }
133
134 #[test]
135 fn test_secret_string_debug_does_not_leak() {
136 const SECRET_VALUE: &str = "my_secret_value";
137 let test_secret = TestSecret::from(SECRET_VALUE);
138
139 let debug_content = format!("{test_secret:?}");
140
141 assert_eq!(
142 debug_content,
143 format!("TestSecret(<REDACTED len: {}>)", SECRET_VALUE.len())
144 );
145 }
146}