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 rigetti_pyo3::impl_repr;
17
18macro_rules! make_secret_string {
21 (
22 $(#[$attr:meta])*
23 $name:ident
24 ) => {
25 #[derive(Default, Clone, PartialEq, Eq, Deserialize, Serialize)]
26 #[serde(transparent)]
27 #[cfg_attr(not(feature = "python"), ::optipy::strip_pyo3)]
28 #[cfg_attr(feature = "stubs", ::pyo3_stub_gen::derive::gen_stub_pyclass)]
29 #[cfg_attr(feature = "python",
30 ::pyo3::pyclass(module = "qcs_api_client_common.configuration", eq, frozen, skip_from_py_object))]
31 $(#[$attr])*
32 pub struct $name(Cow<'static, str>);
33
34 impl fmt::Debug for $name {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 const NAME: &str = stringify!($name);
37 let len = self.0.len();
38 write!(f, "{NAME}(<REDACTED len: {len}>)")
39 }
40 }
41
42 impl<T: Into<Cow<'static, str>>> From<T> for $name {
43 fn from(value: T) -> Self {
44 Self(value.into())
45 }
46 }
47
48 #[cfg_attr(not(feature = "python"), ::optipy::strip_pyo3)]
49 #[cfg_attr(feature = "stubs", ::pyo3_stub_gen::derive::gen_stub_pymethods)]
50 #[cfg_attr(feature = "python", ::pyo3::pymethods)]
51 impl $name {
52 #[must_use]
53 #[getter(is_empty)]
54 pub fn is_empty(&self) -> bool {
56 self.0.is_empty()
57 }
58
59 #[must_use]
60 #[getter(secret)]
61 pub fn secret(&self) -> &str {
63 self.0.as_ref()
64 }
65 }
66
67 #[cfg(feature = "python")]
68 impl_repr!($name);
69
70 #[cfg(feature = "python")]
71 #[cfg_attr(feature = "stubs", ::pyo3_stub_gen::derive::gen_stub_pymethods)]
72 #[::pyo3::pymethods]
73 impl $name {
74 #[new]
75 pub(crate) fn __new__(value: String) -> Self {
76 Self::from(value)
77 }
78 }
79 }
80}
81
82make_secret_string!(
83 SecretRefreshToken
85);
86
87make_secret_string!(
88 SecretAccessToken
90);
91
92make_secret_string!(
93 ClientSecret
95);
96
97#[cfg(test)]
98mod test {
99 use super::*;
100
101 make_secret_string!(TestSecret);
102
103 #[test]
104 fn test_secret_string_serialization() {
105 const SECRET_VALUE: &str = "my_secret_value";
106 const SECRET_VALUE_JSON: &str = "\"my_secret_value\"";
107
108 assert_eq!(
110 serde_json::to_value(TestSecret::from(SECRET_VALUE)).unwrap(),
111 serde_json::Value::String(SECRET_VALUE.to_string()),
112 );
113
114 let test_secret: TestSecret = serde_json::from_str(SECRET_VALUE_JSON).unwrap();
115 assert_eq!(test_secret.secret(), SECRET_VALUE);
116
117 assert_eq!(
118 serde_json::to_string(&test_secret).unwrap(),
119 SECRET_VALUE_JSON
120 );
121 }
122
123 #[test]
124 fn test_secret_string_debug_does_not_leak() {
125 const SECRET_VALUE: &str = "my_secret_value";
126 let test_secret = TestSecret::from(SECRET_VALUE);
127
128 let debug_content = format!("{test_secret:?}");
129
130 assert_eq!(
131 debug_content,
132 format!("TestSecret(<REDACTED len: {}>)", SECRET_VALUE.len())
133 );
134 }
135}