1use std::fmt;
2
3use serde::{Deserialize, Deserializer};
4use sofie_core::{Cast, ColumnReader, ColumnSpec, Result, SqlValue};
5
6#[derive(Clone, PartialEq, Eq)]
8pub struct Secret<T> {
9 value: T,
10}
11
12impl<T> Secret<T> {
13 pub fn new(value: T) -> Self {
14 Self { value }
15 }
16
17 pub fn expose(&self) -> &T {
18 &self.value
19 }
20}
21
22impl<T> fmt::Debug for Secret<T> {
23 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
24 sofie_core::value::write_masked(formatter, "Secret")
25 }
26}
27
28impl<T: Cast> Cast for Secret<T> {
29 const COLUMNS: &'static [ColumnSpec] = T::COLUMNS;
30 const NULLABLE: bool = T::NULLABLE;
31 const SENSITIVE: bool = true;
32
33 fn encode(&self, out: &mut Vec<SqlValue>) {
34 self.value.encode(out);
35 }
36
37 fn decode(row: &mut dyn ColumnReader) -> Result<Self> {
38 T::decode(row).map(Self::new)
39 }
40}
41
42impl<'de, T: Deserialize<'de>> Deserialize<'de> for Secret<T> {
43 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
44 T::deserialize(deserializer).map(Self::new)
45 }
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51 use crate::testing::round_trip;
52
53 #[test]
54 fn never_prints_its_value() {
55 let key = Secret::new("sk_live_123".to_owned());
56
57 assert_eq!(format!("{key:?}"), "Secret(***)");
58 assert_eq!(format!("{:?}", Some(&key)), "Some(Secret(***))");
59 assert_eq!(key.expose(), "sk_live_123");
60 }
61
62 #[test]
63 fn is_read_from_input_and_stored_like_its_value() {
64 let key: Secret<String> = serde_json::from_value(serde_json::json!("sk_123")).unwrap();
65 assert_eq!(round_trip(&key), vec![SqlValue::Text("sk_123".into())]);
66 }
67}