From f8bd35fa8b2f5c128360fdd64944c0621e150b01 Mon Sep 17 00:00:00 2001 From: Ethan Brierley Date: Mon, 24 Jul 2023 20:53:29 +0100 Subject: [PATCH] Enable `serde::expose_secret` to deserialize `Option>` (#24) * Experiment with more flexiable `expose_secret` that supports `Option` * `SerializableSecret` * Add test --- Cargo.toml | 4 +++- src/serde.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 274e189..27e26df 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,12 +19,14 @@ std = [] fake = ["dep:fake", "dep:rand"] [dependencies] -serde = { version = "1.0", optional = true, default-features = false } fake = { version = "2.5", optional = true, default-features = false } rand = { version = "0.8", optional = true, default-features = false } +serde = { version = "1.0", optional = true, default-features = false } [dev-dependencies] +fake = { version = "2.5", features = ["derive"] } serde = { version = "1.0", features = ["derive"] } +serde_json = "1.0" [package.metadata.docs.rs] all-features = true diff --git a/src/serde.rs b/src/serde.rs index 7b33ccf..7b8052e 100644 --- a/src/serde.rs +++ b/src/serde.rs @@ -10,6 +10,36 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Secret { } } +/// A serializable type that contains a secret. +/// +/// This abstraction enables [expose_secret] to be used to serialize both `Secret` and +/// `Option`. +pub trait SerializableSecret { + type Exposed<'a>: Serialize + where + Self: 'a; + /// To reduce the number of functions that are able to expose secrets we require + /// that the [`Secret::expose_secret`] function is passed in here. + fn expose_via<'a>(&'a self, expose: impl Fn(&Secret) -> &T) -> Self::Exposed<'a>; +} + +impl SerializableSecret for Secret { + type Exposed<'a> = &'a T where T: 'a; + + fn expose_via<'a>(&'a self, expose: impl Fn(&Secret) -> &T) -> Self::Exposed<'a> { + expose(self) + } +} + +impl SerializableSecret for Option> { + type Exposed<'a> = Option<&'a T> where T: 'a; + + fn expose_via<'a>(&'a self, expose: impl Fn(&Secret) -> &T) -> Self::Exposed<'a> { + self.as_ref().map(expose) + } +} + +#[cfg(feature = "serde")] /// Exposes a [Secret] for serialization. /// /// For general-purpose secret exposing see [Secret::expose_secret]. @@ -19,8 +49,36 @@ impl<'de, T: Deserialize<'de>> Deserialize<'de> for Secret { /// *This API requires the following crate features to be activated: `serde`* #[inline] pub fn expose_secret( - secret: &Secret, + secret: &impl SerializableSecret, serializer: S, ) -> Result { - secret.expose_secret().serialize(serializer) + secret + .expose_via(Secret::expose_secret) + .serialize(serializer) +} + +#[cfg(test)] +mod tests { + use super::*; + use fake::{Fake, Faker}; + use serde::{Deserialize, Serialize}; + + #[test] + fn deserialize_the_serialized() { + #[derive(Serialize, Deserialize, fake::Dummy, PartialEq, Debug)] + struct Test { + #[serde(serialize_with = "expose_secret")] + one: Secret, + #[serde(serialize_with = "expose_secret")] + two: Option>, + } + + let to_serialize: Test = Faker.fake(); + + let serialized = serde_json::to_string(&to_serialize).unwrap(); + + let deserialized = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(to_serialize, deserialized) + } }