smart_config/de/secret.rs
1use secrecy::{ExposeSecret, SecretString};
2
3use super::{DeserializeContext, DeserializeParam, WellKnown, WellKnownOption};
4use crate::{
5 error::ErrorWithOrigin,
6 metadata::{BasicTypes, ParamMetadata, TypeDescription},
7 value::{StrValue, Value},
8};
9
10/// Deserializer for secret strings (any type convertible from [`SecretString`], including `SecretString` itself).
11/// Will set the corresponding flag for [`ParamMetadata`], making raw param value hidden in the debug output etc.
12///
13/// # Examples
14///
15/// ```
16/// use secrecy::ExposeSecret;
17/// # use smart_config::{testing, DescribeConfig, DeserializeConfig};
18///
19/// #[derive(DescribeConfig, DeserializeConfig)]
20/// struct TestConfig {
21/// secret: secrecy::SecretString,
22/// }
23///
24/// let input = smart_config::config!("secret": "correct horse battery staple");
25/// let config: TestConfig = testing::test(input)?;
26/// assert_eq!(config.secret.expose_secret(), "correct horse battery staple");
27/// # anyhow::Ok(())
28/// ```
29#[derive(Debug)]
30pub struct FromSecretString;
31
32impl<T: From<SecretString> + ExposeSecret<str>> DeserializeParam<T> for FromSecretString {
33 const EXPECTING: BasicTypes = BasicTypes::STRING;
34
35 fn describe(&self, description: &mut TypeDescription) {
36 description.set_secret();
37 }
38
39 fn deserialize_param(
40 &self,
41 ctx: DeserializeContext<'_>,
42 param: &'static ParamMetadata,
43 ) -> Result<T, ErrorWithOrigin> {
44 let de = ctx.current_value_deserializer(param.name)?;
45 let s: SecretString = match de.value() {
46 Value::String(StrValue::Secret(s)) => s.clone(),
47 Value::String(StrValue::Plain(s)) => s.clone().into(),
48 _ => return Err(de.invalid_type("secret string")),
49 };
50 Ok(s.into())
51 }
52
53 fn serialize_param(&self, param: &T) -> serde_json::Value {
54 param.expose_secret().into()
55 }
56}
57
58impl WellKnown for SecretString {
59 type Deserializer = FromSecretString;
60 const DE: Self::Deserializer = FromSecretString;
61}
62
63impl WellKnownOption for SecretString {}
64
65/// Deserializer for arbitrary secret params. Will set the corresponding flag for [`ParamMetadata`],
66/// making raw param value hidden in the debug output etc.
67///
68/// Can be used by placing `#[serde(secret)]` on the param.
69///
70/// **Important.** The deserializer does not hide the deserialized value of the param! You are responsible
71/// for doing it by selecting an appropriate param type (e.g., one that zeroizes its contents on drop).
72///
73/// # Examples
74///
75/// ```
76/// use secrecy::{ExposeSecret, ExposeSecretMut, SecretBox};
77/// use serde::{Deserialize, Deserializer, Serialize, Serializer};
78/// use smart_config::{de::Serde, testing, DescribeConfig, DeserializeConfig};
79///
80/// // It is generally a good idea to wrap a secret into a `SecretBox`
81/// // so that it is zeroized on drop and has an opaque `Debug` representation.
82/// #[derive(Debug)]
83/// struct NumSecret(SecretBox<u64>);
84///
85/// impl Serialize for NumSecret {
86/// fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
87/// // Serialize the underlying secret
88/// self.0.expose_secret().serialize(serializer)
89/// }
90/// }
91///
92/// impl<'de> serde::Deserialize<'de> for NumSecret {
93/// fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
94/// // Deserialize a `u64` and wrap it into a secret.
95/// let mut secret = SecretBox::default();
96/// *secret.expose_secret_mut() = u64::deserialize(deserializer)?;
97/// Ok(Self(secret))
98/// }
99/// }
100///
101/// #[derive(DescribeConfig, DeserializeConfig)]
102/// struct TestConfig {
103/// // Secret values must be deserializable from a string
104/// // because all secrets are strings. Because of type coercion, a `u64` deserializer
105/// // will work correctly if supplied with a string, which we express
106/// // through `Serde![]` args.
107/// #[config(secret, with = Serde![int, str])]
108/// secret: NumSecret,
109/// }
110///
111/// let input = smart_config::config!("secret": "123");
112/// let config: TestConfig = testing::test(input)?;
113/// assert_eq!(*config.secret.0.expose_secret(), 123);
114/// # anyhow::Ok(())
115/// ```
116#[derive(Debug)]
117pub struct Secret<De>(pub De);
118
119impl<T, De> DeserializeParam<T> for Secret<De>
120where
121 De: DeserializeParam<T>,
122{
123 const EXPECTING: BasicTypes = {
124 assert!(
125 De::EXPECTING.contains(BasicTypes::STRING),
126 "must be able to deserialize from string"
127 );
128 BasicTypes::STRING
129 };
130
131 fn describe(&self, description: &mut TypeDescription) {
132 self.0.describe(description);
133 description.set_secret();
134 }
135
136 fn deserialize_param(
137 &self,
138 ctx: DeserializeContext<'_>,
139 param: &'static ParamMetadata,
140 ) -> Result<T, ErrorWithOrigin> {
141 self.0.deserialize_param(ctx, param)
142 }
143
144 fn serialize_param(&self, param: &T) -> serde_json::Value {
145 self.0.serialize_param(param)
146 }
147}